diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy index 0cd64dcfd41fd..ed642f22cfeb4 100644 --- a/.ci/end2end.groovy +++ b/.ci/end2end.groovy @@ -76,7 +76,7 @@ pipeline { } } steps{ - notifyStatus('Running smoke tests', 'PENDING') + notifyTestStatus('Running smoke tests', 'PENDING') dir("${BASE_DIR}"){ sh "${E2E_DIR}/ci/run-e2e.sh" } @@ -95,10 +95,10 @@ pipeline { } } unsuccessful { - notifyStatus('Test failures', 'FAILURE') + notifyTestStatus('Test failures', 'FAILURE') } success { - notifyStatus('Tests passed', 'SUCCESS') + notifyTestStatus('Tests passed', 'SUCCESS') } } } @@ -113,5 +113,9 @@ pipeline { } def notifyStatus(String description, String status) { - withGithubNotify.notify('end2end-for-apm-ui', description, status, getBlueoceanDisplayURL()) + withGithubNotify.notify('end2end-for-apm-ui', description, status, getBlueoceanTabURL('pipeline')) +} + +def notifyTestStatus(String description, String status) { + withGithubNotify.notify('end2end-for-apm-ui', description, status, getBlueoceanTabURL('tests')) } diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index 11f9ccaeddb1e..e4b5e35e1e4a9 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -2,59 +2,5 @@ set -e -branch="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" - -# run setup script that gives us node, yarn, and bootstraps the project -source src/dev/ci_setup/setup.sh; - -# download es snapshots -node scripts/es snapshot --download-only; -node scripts/es snapshot --license=oss --download-only; - -# download reporting browsers -(cd "x-pack" && yarn gulp prepare); - -# cache the chromedriver archive -chromedriverDistVersion="$(node -e "console.log(require('chromedriver').version)")" -chromedriverPkgVersion="$(node -e "console.log(require('./package.json').devDependencies.chromedriver)")" -if [ -z "$chromedriverDistVersion" ] || [ -z "$chromedriverPkgVersion" ]; then - echo "UNABLE TO DETERMINE CHROMEDRIVER VERSIONS" - exit 1 -fi -mkdir -p .chromedriver -curl "https://chromedriver.storage.googleapis.com/$chromedriverDistVersion/chromedriver_linux64.zip" > .chromedriver/chromedriver.zip -echo "$chromedriverPkgVersion" > .chromedriver/pkgVersion - -# cache the geckodriver archive -geckodriverPkgVersion="$(node -e "console.log(require('./package.json').devDependencies.geckodriver)")" -if [ -z "$geckodriverPkgVersion" ]; then - echo "UNABLE TO DETERMINE geckodriver VERSIONS" - exit 1 -fi -mkdir -p ".geckodriver" -cp "node_modules/geckodriver/geckodriver.tar.gz" .geckodriver/geckodriver.tar.gz -echo "$geckodriverPkgVersion" > .geckodriver/pkgVersion - -echo "Creating bootstrap_cache archive" - -# archive cacheable directories -mkdir -p "$HOME/.kibana/bootstrap_cache" -tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ - x-pack/plugins/reporting/.chromium \ - .es \ - .chromedriver \ - .geckodriver; - -echo "Adding node_modules" -# Find all of the node_modules directories that aren't test fixtures, and aren't inside other node_modules directories, and append them to the tar -find . -type d -name node_modules -not -path '*__fixtures__*' -prune -print0 | xargs -0I % tar -rf "$HOME/.kibana/bootstrap_cache/$branch.tar" "%" - -echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" - -if [ "$branch" == "master" ]; then - echo "Creating bootstrap cache for 7.x"; - - git clone https://github.com/elastic/kibana.git --branch 7.x --depth 1 /tmp/kibana-7.x - (cd /tmp/kibana-7.x && ./.ci/packer_cache.sh); - rm -rf /tmp/kibana-7.x; -fi +./.ci/packer_cache_for_branch.sh master +./.ci/packer_cache_for_branch.sh 7.x diff --git a/.ci/packer_cache_for_branch.sh b/.ci/packer_cache_for_branch.sh new file mode 100755 index 0000000000000..a9fbe781915b6 --- /dev/null +++ b/.ci/packer_cache_for_branch.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +set -e + +branch="$1" +checkoutDir="$(pwd)" + +if [[ "$branch" != "master" ]]; then + checkoutDir="/tmp/kibana-$branch" + git clone https://github.com/elastic/kibana.git --branch "$branch" --depth 1 "$checkoutDir" + cd "$checkoutDir" +fi + +source src/dev/ci_setup/setup.sh; + +# download es snapshots +node scripts/es snapshot --download-only; +node scripts/es snapshot --license=oss --download-only; + +# download reporting browsers +(cd "x-pack" && yarn gulp prepare); + +# cache the chromedriver archive +chromedriverDistVersion="$(node -e "console.log(require('chromedriver').version)")" +chromedriverPkgVersion="$(node -e "console.log(require('./package.json').devDependencies.chromedriver)")" +if [ -z "$chromedriverDistVersion" ] || [ -z "$chromedriverPkgVersion" ]; then + echo "UNABLE TO DETERMINE CHROMEDRIVER VERSIONS" + exit 1 +fi +mkdir -p .chromedriver +curl "https://chromedriver.storage.googleapis.com/$chromedriverDistVersion/chromedriver_linux64.zip" > .chromedriver/chromedriver.zip +echo "$chromedriverPkgVersion" > .chromedriver/pkgVersion + +# cache the geckodriver archive +geckodriverPkgVersion="$(node -e "console.log(require('./package.json').devDependencies.geckodriver)")" +if [ -z "$geckodriverPkgVersion" ]; then + echo "UNABLE TO DETERMINE geckodriver VERSIONS" + exit 1 +fi +mkdir -p ".geckodriver" +cp "node_modules/geckodriver/geckodriver.tar.gz" .geckodriver/geckodriver.tar.gz +echo "$geckodriverPkgVersion" > .geckodriver/pkgVersion + +echo "Creating bootstrap_cache archive" + +# archive cacheable directories +mkdir -p "$HOME/.kibana/bootstrap_cache" +tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ + x-pack/plugins/reporting/.chromium \ + .es \ + .chromedriver \ + .geckodriver; + +echo "Adding node_modules" +# Find all of the node_modules directories that aren't test fixtures, and aren't inside other node_modules directories, and append them to the tar +find . -type d -name node_modules -not -path '*__fixtures__*' -prune -print0 | xargs -0I % tar -rf "$HOME/.kibana/bootstrap_cache/$branch.tar" "%" + +echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" + +if [[ "$branch" != "master" ]]; then + rm --preserve-root -rf "$checkoutDir" +fi diff --git a/.eslintrc.js b/.eslintrc.js index 9657719f0f526..8d5b4525d51ba 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -472,6 +472,7 @@ module.exports = { { files: [ 'test/functional/services/lib/web_element_wrapper/scroll_into_view_if_necessary.js', + 'src/legacy/ui/ui_render/bootstrap/kbn_bundles_loader_source.js', '**/browser_exec_scripts/**/*.js', ], rules: { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 00573e04396b4..e6f6e83253c8b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,7 @@ # App /x-pack/plugins/dashboard_enhanced/ @elastic/kibana-app +/x-pack/plugins/discover_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app @@ -66,11 +67,10 @@ # APM /x-pack/plugins/apm/ @elastic/apm-ui -/x-pack/plugins/apm/ @elastic/apm-ui /x-pack/test/functional/apps/apm/ @elastic/apm-ui /src/legacy/core_plugins/apm_oss/ @elastic/apm-ui /src/plugins/apm_oss/ @elastic/apm-ui -/src/apm.js @watson +/src/apm.js @watson @vigneshshanmugam # Beats /x-pack/legacy/plugins/beats_management/ @elastic/beats diff --git a/NOTICE.txt b/NOTICE.txt index 33c1d535d7df3..946b328b8766c 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -21,6 +21,11 @@ used. Logarithmic ticks are places at powers of ten and at half those values if there are not to many ticks already (e.g. [1, 5, 10, 50, 100]). For details, see https://github.com/flot/flot/pull/1328 +--- +This module was heavily inspired by the externals plugin that ships with webpack@97d58d31 +MIT License http://www.opensource.org/licenses/mit-license.php +Author Tobias Koppers @sokra + --- This product has relied on ASTExplorer that is licensed under MIT. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md index fd05ae139ba21..80e2702451d86 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.links.md @@ -8,6 +8,9 @@ ```typescript readonly links: { + readonly dashboard: { + readonly drilldowns: string; + }; readonly filebeat: { readonly base: string; readonly installation: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md index 1114e05589c4b..9e7938bd9c850 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinkssetup.md @@ -17,5 +17,5 @@ export interface DocLinksSetup | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinkssetup.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinkssetup.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinkssetup.links.md) | {
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinkssetup.links.md) | {
readonly dashboard: {
readonly drilldowns: 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 filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 147a72016b235..a45bd3d44b28a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -157,6 +157,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsExportResultDetails](./kibana-plugin-core-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | | [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) | | | [SavedObjectsFindResponse](./kibana-plugin-core-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsFindResult](./kibana-plugin-core-server.savedobjectsfindresult.md) | | | [SavedObjectsImportConflictError](./kibana-plugin-core-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | | [SavedObjectsImportError](./kibana-plugin-core-server.savedobjectsimporterror.md) | Represents a failure to import. | | [SavedObjectsImportMissingReferencesError](./kibana-plugin-core-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md new file mode 100644 index 0000000000000..c46e60f2ecf6d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [extraPublicDirs](./kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md) + +## PluginManifest.extraPublicDirs property + +> Warning: This API is now obsolete. +> +> + +Specifies directory names that can be imported by other ui-plugins built using the same instance of the @kbn/optimizer. A temporary measure we plan to replace with better mechanisms for sharing static code between plugins + +Signature: + +```typescript +readonly extraPublicDirs?: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md index fe0ca476bbcb2..5edee51d6c523 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md @@ -21,6 +21,7 @@ Should never be used in code outside of Core but is exported for documentation p | Property | Type | Description | | --- | --- | --- | | [configPath](./kibana-plugin-core-server.pluginmanifest.configpath.md) | ConfigPath | Root [configuration path](./kibana-plugin-core-server.configpath.md) used by the plugin, defaults to "id" in snake\_case format. | +| [extraPublicDirs](./kibana-plugin-core-server.pluginmanifest.extrapublicdirs.md) | string[] | Specifies directory names that can be imported by other ui-plugins built using the same instance of the @kbn/optimizer. A temporary measure we plan to replace with better mechanisms for sharing static code between plugins | | [id](./kibana-plugin-core-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. | | [kibanaVersion](./kibana-plugin-core-server.pluginmanifest.kibanaversion.md) | string | The version of Kibana the plugin is compatible with, defaults to "version". | | [optionalPlugins](./kibana-plugin-core-server.pluginmanifest.optionalplugins.md) | readonly PluginName[] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.md index a1b1a7a056206..4ed069d1598fe 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.md @@ -20,6 +20,6 @@ export interface SavedObjectsFindResponse | --- | --- | --- | | [page](./kibana-plugin-core-server.savedobjectsfindresponse.page.md) | number | | | [per\_page](./kibana-plugin-core-server.savedobjectsfindresponse.per_page.md) | number | | -| [saved\_objects](./kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md) | Array<SavedObject<T>> | | +| [saved\_objects](./kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md) | Array<SavedObjectsFindResult<T>> | | | [total](./kibana-plugin-core-server.savedobjectsfindresponse.total.md) | number | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md index adad0dd2b1176..7a91367f6ef0b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresponse.saved_objects.md @@ -7,5 +7,5 @@ Signature: ```typescript -saved_objects: Array>; +saved_objects: Array>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md new file mode 100644 index 0000000000000..e455074a7d11b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsFindResult](./kibana-plugin-core-server.savedobjectsfindresult.md) + +## SavedObjectsFindResult interface + + +Signature: + +```typescript +export interface SavedObjectsFindResult extends SavedObject +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [score](./kibana-plugin-core-server.savedobjectsfindresult.score.md) | number | The Elasticsearch _score of this result. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.score.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.score.md new file mode 100644 index 0000000000000..c6646df6ee470 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindresult.score.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsFindResult](./kibana-plugin-core-server.savedobjectsfindresult.md) > [score](./kibana-plugin-core-server.savedobjectsfindresult.score.md) + +## SavedObjectsFindResult.score property + +The Elasticsearch `_score` of this result. + +Signature: + +```typescript +score: number; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md index a9bb8f1eb9d6d..3f5e4ba0f7799 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.irequesttypesmap.md @@ -4,6 +4,8 @@ ## IRequestTypesMap interface +The map of search strategy IDs to the corresponding request type definitions. + Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md index fe5fa0a5d3a33..629ab4347eda8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iresponsetypesmap.md @@ -4,6 +4,8 @@ ## IResponseTypesMap interface +The map of search strategy IDs to the corresponding response type definitions. + Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md index 6e037f5161b53..96991579c1716 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearch.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ISearch = (request: IRequestTypesMap[T], options?: ISearchOptions) => Promise; +export declare type ISearch = (context: RequestHandlerContext, request: IRequestTypesMap[T], options?: ISearchOptions) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md index 99c30515e8da6..b5a687d1b19d8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcancel.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ISearchCancel = (id: string) => Promise; +export declare type ISearchCancel = (context: RequestHandlerContext, id: string) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.config_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.config_.md deleted file mode 100644 index 364d44dba758a..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.config_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) > [config$](./kibana-plugin-plugins-data-server.isearchcontext.config_.md) - -## ISearchContext.config$ property - -Signature: - -```typescript -config$: Observable; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.core.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.core.md deleted file mode 100644 index 9d571c25d94bd..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.core.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) > [core](./kibana-plugin-plugins-data-server.isearchcontext.core.md) - -## ISearchContext.core property - -Signature: - -```typescript -core: CoreSetup; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.md deleted file mode 100644 index 1c3c5ec78f894..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchcontext.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) - -## ISearchContext interface - -Signature: - -```typescript -export interface ISearchContext -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [config$](./kibana-plugin-plugins-data-server.isearchcontext.config_.md) | Observable<SharedGlobalConfig> | | -| [core](./kibana-plugin-plugins-data-server.isearchcontext.core.md) | CoreSetup | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index 0319048f4418b..49412fc42d3b5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -14,5 +14,5 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | -| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | AbortSignal | | +| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md index 7da5c182b2e0f..948dfd66da7a0 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md @@ -4,6 +4,8 @@ ## ISearchOptions.signal property +An `AbortSignal` that allows the caller of `search` to abort a search request. + Signature: ```typescript diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md new file mode 100644 index 0000000000000..93e253b2e98a3 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) + +## ISearchSetup interface + +Signature: + +```typescript +export interface ISearchSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | TRegisterSearchStrategy | Extension point exposed for other plugins to register their own search strategies. | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md new file mode 100644 index 0000000000000..c06b8b00806bf --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) > [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) + +## ISearchSetup.registerSearchStrategy property + +Extension point exposed for other plugins to register their own search strategies. + +Signature: + +```typescript +registerSearchStrategy: TRegisterSearchStrategy; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md new file mode 100644 index 0000000000000..0ba4bf578d6cc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) > [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) + +## ISearchStart.getSearchStrategy property + +Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. + +Signature: + +```typescript +getSearchStrategy: TGetSearchStrategy; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md new file mode 100644 index 0000000000000..abe72396f61e1 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) + +## ISearchStart interface + +Signature: + +```typescript +export interface ISearchStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | TGetSearchStrategy | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md new file mode 100644 index 0000000000000..c1e0c3d9f2330 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) > [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) + +## ISearchStrategy.cancel property + +Signature: + +```typescript +cancel?: ISearchCancel; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md new file mode 100644 index 0000000000000..167c6ab6e5a16 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) + +## ISearchStrategy interface + +Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. + +Signature: + +```typescript +export interface ISearchStrategy +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | ISearchCancel<T> | | +| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | ISearch<T> | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md new file mode 100644 index 0000000000000..34a17ca87807a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) > [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) + +## ISearchStrategy.search property + +Signature: + +```typescript +search: ISearch; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 0efbe8ed4ed64..f492ba2843a69 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -39,10 +39,12 @@ | [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Use data plugin interface instead | | [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) | | -| [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) | | -| [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) | | -| [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) | | +| [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) | The map of search strategy IDs to the corresponding request type definitions. | +| [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) | The map of search strategy IDs to the corresponding response type definitions. | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | +| [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | +| [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | +| [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. | | [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | | [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | | @@ -73,5 +75,5 @@ | [ISearch](./kibana-plugin-plugins-data-server.isearch.md) | | | [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | -| [TSearchStrategyProvider](./kibana-plugin-plugins-data-server.tsearchstrategyprovider.md) | Search strategy provider creates an instance of a search strategy with the request handler context bound to it. This way every search strategy can use whatever information they require from the request context. | +| [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md) | Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via:const MY\_STRATEGY = 'MY\_STRATEGY';declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; }export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } } | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md index bd617990a00a2..13c69d6bf7548 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md @@ -7,11 +7,11 @@ Signature: ```typescript -setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): { +setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): { + search: ISearchSetup; fieldFormats: { register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; - search: ISearchSetup; }; ``` @@ -19,15 +19,15 @@ setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): { | Parameter | Type | Description | | --- | --- | --- | -| core | CoreSetup | | +| core | CoreSetup<object, DataPluginStart> | | | { usageCollection } | DataPluginSetupDependencies | | Returns: `{ + search: ISearchSetup; fieldFormats: { register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; - search: ISearchSetup; }` 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 2a30cd3e68158..2c7a833ab641b 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 @@ -8,8 +8,9 @@ ```typescript start(core: CoreStart): { + search: ISearchStart; fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; }; ``` @@ -23,8 +24,9 @@ start(core: CoreStart): { Returns: `{ + search: ISearchStart; fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md index b7d6a7e8a83fd..1377d82123d41 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md @@ -15,4 +15,5 @@ export interface DataPluginStart | Property | Type | Description | | --- | --- | --- | | [fieldFormats](./kibana-plugin-plugins-data-server.pluginstart.fieldformats.md) | FieldFormatsStart | | +| [search](./kibana-plugin-plugins-data-server.pluginstart.search.md) | ISearchStart | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.search.md new file mode 100644 index 0000000000000..3144d8c40b780 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.search.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) > [search](./kibana-plugin-plugins-data-server.pluginstart.search.md) + +## PluginStart.search property + +Signature: + +```typescript +search: ISearchStart; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tsearchstrategyprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tsearchstrategyprovider.md deleted file mode 100644 index f528f48a68f72..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tsearchstrategyprovider.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TSearchStrategyProvider](./kibana-plugin-plugins-data-server.tsearchstrategyprovider.md) - -## TSearchStrategyProvider type - -Search strategy provider creates an instance of a search strategy with the request handler context bound to it. This way every search strategy can use whatever information they require from the request context. - -Signature: - -```typescript -export declare type TSearchStrategyProvider = (context: ISearchContext, caller: APICaller, search: ISearchGeneric) => ISearchStrategy; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md new file mode 100644 index 0000000000000..443d8d1b424d0 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tstrategytypes.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md) + +## TStrategyTypes type + +Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via: + +const MY\_STRATEGY = 'MY\_STRATEGY'; + +declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; } + +export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } } + +Signature: + +```typescript +export declare type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; +``` diff --git a/docs/management/managing-beats.asciidoc b/docs/management/managing-beats.asciidoc index 26998a3b5b8f4..d5a9c52feae23 100644 --- a/docs/management/managing-beats.asciidoc +++ b/docs/management/managing-beats.asciidoc @@ -21,7 +21,7 @@ Don't have a license? You can start a 30-day trial. Open the menu, go to *Stack Management > Elasticsearch > License Management*. At the end of the trial period, you can purchase a subscription to keep using central management. For more information, see https://www.elastic.co/subscriptions and -{stack-ov}/license-management.html[License Management]. +<>. ==== {kib} makes it easy for you to use central management by walking you through the diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index d7740d76b0456..2b88ffe2e2dda 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -1,17 +1,17 @@ [role="xpack"] [[maps-connect-to-ems]] -== Connecting to Elastic Maps Service +== Connect to Elastic Maps Service https://www.elastic.co/elastic-maps-service[Elastic Maps Service (EMS)] is a service that hosts tile layers and vector shapes of administrative boundaries. -If you are using Kibana's out-of-the-box settings, **Elastic Maps** is already configured to use EMS. +If you are using Kibana's out-of-the-box settings, Maps is already configured to use EMS. EMS requests are made to the following domains: * tiles.maps.elastic.co * vector.maps.elastic.co -**Elastic Maps** makes requests directly from the browser to EMS. +Maps makes requests directly from the browser to EMS. [float] === Connect to Elastic Maps Service from an internal network @@ -33,5 +33,5 @@ behind a firewall. If this happens, you can disable the EMS connection to avoid To disable EMS, change your <> file. . Set `map.includeElasticMapsService` to `false` to turn off the EMS connection. -. Set `map.tilemap.url` to the URL of your tile server. This configures the default tile layer of **Elastic Maps**. +. Set `map.tilemap.url` to the URL of your tile server. This configures the default tile layer of Maps. . (Optional) Set `map.regionmap` to the vector shapes of the administrative boundaries that you want to use. diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index 5618e5ab0bd16..6c28840087252 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -1,11 +1,13 @@ [role="xpack"] [[geojson-upload]] == Upload GeoJSON data -*Elastic Maps* makes it easy to import geospatial data into the Elastic Stack. -Using the *GeoJSON Upload* feature, you can drag and drop your point and shape + +Maps makes it easy to import geospatial data into the Elastic Stack. +Using the GeoJSON Upload feature, you can drag and drop your point and shape data files directly into {es}, and then use them as layers in the map. You can also use the GeoJSON data in the broader Kibana ecosystem, for example, in visualizations and Canvas workpads. + [float] === Why GeoJSON? GeoJSON is an open-standard file format for storing geospatial vector data. @@ -17,7 +19,7 @@ GeoJSON is the most commonly used and flexible option. Follow these instructions to upload a GeoJSON data file, or try the <>. -. Open the menu, go to *Elastic Maps*, and then click *Add layer*. +. Open the menu, go to *Maps*, and then click *Add layer*. . Click *Uploaded GeoJSON*. + [role="screenshot"] diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index 6480d64bdd174..8999b9fe20b11 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -1,13 +1,13 @@ [role="xpack"] [[maps]] -= Elastic Maps += Maps [partintro] -- -*Elastic Maps* enables you to parse through your geographical data at scale, with speed, and in real time. With features like multiple layers and indices in a map, plotting of raw documents, dynamic client-side styling, and global search across multiple layers, you can understand and monitor your data with ease. +Maps enables you to parse through your geographical data at scale, with speed, and in real time. With features like multiple layers and indices in a map, plotting of raw documents, dynamic client-side styling, and global search across multiple layers, you can understand and monitor your data with ease. -With *Elastic Maps*, you can: +With Maps, you can: * Create maps with multiple layers and indices. * Upload GeoJSON files into Elasticsearch. @@ -15,7 +15,7 @@ With *Elastic Maps*, you can: * Symbolize features using data values. * Focus in on just the data you want. -*Ready to get started?* Start your tour of *Elastic Maps* with the <>. +*Ready to get started?* Start your tour of Maps with the <>. [float] === Create maps with multiple layers and indices @@ -26,7 +26,7 @@ image::maps/images/sample_data_ecommerce.png[] [float] === Upload GeoJSON files into Elasticsearch -Elastic Maps makes it easy to import geospatial data into the Elastic Stack. Using the GeoJSON Upload feature, you can drag and drop your point and shape data files directly into Elasticsearch, and then use them as layers in the map. +Maps makes it easy to import geospatial data into the Elastic Stack. Using the GeoJSON Upload feature, you can drag and drop your point and shape data files directly into Elasticsearch, and then use them as layers in the map. [float] === Embed your map in dashboards diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index c1ca9d0925c9a..d1a6593f61fe1 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[indexing-geojson-data-tutorial]] -== Indexing GeoJSON data tutorial +=== Tutorial: Index GeoJSON data In this tutorial, you'll build a customized map that shows the flight path between two airports, and the lightning hot spots on that route. You'll learn to: @@ -15,7 +15,7 @@ two airports, and the lightning hot spots on that route. You'll learn to: This tutorial requires you to download the following GeoJSON sample data files. These files are good examples of the types of vector data that you can upload to Kibana and index in -Elasticsearch for display in *Elastic Maps*. +Elasticsearch for display in Maps. * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/logan_international_airport.geojson[Logan International Airport] * https://raw.githubusercontent.com/elastic/examples/master/Maps/Getting%20Started%20Examples/geojson_upload_and_styling/bangor_international_airport.geojson[Bangor International Airport] diff --git a/docs/maps/map-settings.asciidoc b/docs/maps/map-settings.asciidoc index 4e290b6da2e71..e11be438a2237 100644 --- a/docs/maps/map-settings.asciidoc +++ b/docs/maps/map-settings.asciidoc @@ -1,8 +1,8 @@ [role="xpack"] [[maps-settings]] -== Map settings +== Configure map settings -Elastic Maps offers settings that let you configure how a map is displayed. +Maps offers settings that let you configure how a map is displayed. To access these settings, click *Map settings* in the application toolbar. [float] diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 6b03614ab9d6a..872ed1cdedb7e 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -2,6 +2,11 @@ [[maps-aggregations]] == Plot big data without plotting too much data +++++ +Plot big data +++++ + + Use {ref}/search-aggregations.html[aggregations] to plot large data sets without overwhelming your network or your browser. When using aggregations, the documents stay in Elasticsearch and only the calculated values for each group are returned to your computer. @@ -37,7 +42,7 @@ image::maps/images/grid_to_docs.gif[] [[maps-grid-aggregation]] === Grid aggregation -*Grid aggregation* layers use {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into grids. You can calculate metrics for each gridded cell. +Grid aggregation layers use {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into grids. You can calculate metrics for each gridded cell. Symbolize grid aggregation metrics as: @@ -213,7 +218,7 @@ The following shows an example terms aggregation response. Note the *key* proper } -------------------------------------------------- -==== Augmenting the left source with metrics from the right source +==== Augment the left source with metrics from the right source The join adds metrics for each terms aggregation bucket to the world country feature with the corresponding ISO 3166-1 alpha-2 code. Features that do not have a corresponding terms aggregation bucket are not visible on the map. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 239419695138d..09a4dc61cae28 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -1,8 +1,14 @@ [role="xpack"] [[maps-getting-started]] -== Getting started with Elastic Maps +== Get started with Maps -You work with *Elastic Maps* by adding layers. The data for a layer can come from +++++ +Get started +++++ + + + +You work with Maps by adding layers. The data for a layer can come from sources such as {es} documents, vector sources, tile map services, web map services, and more. You can symbolize the data in different ways. For example, you might show which airports have the longest flight @@ -25,7 +31,7 @@ image::maps/images/read-only-badge.png[Example of Maps' read only access indicat [float] === Prerequisites Before you start this tutorial, <>. Each -sample data set includes a map to go along with the data. Once you've added the data, open *Elastic Maps* and +sample data set includes a map to go along with the data. Once you've added the data, open Maps and explore the different layers of the *[Logs] Total Requests and Bytes* map. You'll re-create this map in this tutorial. @@ -40,7 +46,7 @@ In this tutorial, you'll learn to: [role="xpack"] [[maps-create]] -=== Creating a new map +=== Create a map The first thing to do is to create a new map. @@ -55,7 +61,7 @@ image::maps/images/gs_create_new_map.png[] [role="xpack"] [[maps-add-choropleth-layer]] -=== Adding a choropleth layer +=== Add a choropleth layer Now that you have a map, you'll want to add layers to it. The first layer you'll add is a choropleth layer to shade world countries @@ -106,7 +112,7 @@ image::maps/images/gs_add_cloropeth_layer.png[] [role="xpack"] [[maps-add-elasticsearch-layer]] -=== Adding layers for {es} data +=== Add layers for the {es} data To avoid overwhelming the user with too much data at once, you'll add two layers for {es} data. @@ -183,7 +189,7 @@ image::maps/images/sample_data_web_logs.png[] [role="xpack"] [[maps-save]] -=== Saving the map +=== Save the map Now that your map is complete, you'll want to save it so others can use it. . In the application toolbar, click *Save*. @@ -202,7 +208,7 @@ You have completed the steps for re-creating the sample data map. [role="xpack"] [[maps-embedding]] -=== Adding the map to a dashboard +=== Add the map to a dashboard You can add your saved map to a {kibana-ref}/dashboard.html[dashboard] and view your geospatial data alongside bar charts, pie charts, and other visualizations. . Open the menu, then go to *Dashboard*. @@ -224,7 +230,7 @@ Your dashboard should look like this: [role="screenshot"] image::maps/images/gs_dashboard_with_map.png[] -==== Exploring your data using filters +==== Explore your data using filters You can apply filters to your dashboard to hone in on the data that you are most interested in. The dashboard is interactive--you can quickly create filters by clicking on the desired data in the map and visualizations. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 124a976c009d4..0c4042a37f700 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -1,8 +1,8 @@ [role="xpack"] [[maps-search]] -== Searching your data +== Search geographic data -**Elastic Maps** embeds the search bar for real-time search. +Maps embeds the search bar for real-time search. Only layers requesting data from {es} are filtered when you submit a search request. Layers narrowed by the search context contain the filter icon image:maps/images/filter_icon.png[] next to the layer name in the legend. @@ -23,7 +23,7 @@ image::maps/images/global_search_bar.png[] [role="xpack"] [[maps-create-filter-from-map]] -=== Creating filters from your map +=== Create filters from a map You can create two types of filters by interacting with your map: @@ -62,7 +62,7 @@ image::maps/images/create_phrase_filter.png[] [role="xpack"] [[maps-layer-based-filtering]] -=== Filtering a single layer +=== Filter a single layer You can apply a search request to individual layers by setting `Filters` in the layer details panel. Click the *Add filter* button to add a filter to a layer. @@ -74,7 +74,7 @@ image::maps/images/layer_search.png[] [role="xpack"] [[maps-search-across-multiple-indices]] -=== Searching across multiple indices +=== Search across multiple indices Your map might contain multiple {es} indices. This can occur when your map contains two or more layers with {es} sources from different indices. @@ -85,7 +85,7 @@ The most common cause for empty layers are searches for a field that exists in o [float] [[maps-disable-search-for-layer]] -==== Disable search for layer +==== Disable search for a layer You can prevent the search bar from applying search context to a layer by configuring the following: @@ -95,7 +95,7 @@ You can prevent the search bar from applying search context to a layer by config [float] [[maps-add-index-search]] -==== Use _index in your search +==== Use _index in a search Add {ref}/mapping-index-field.html[_index] to your search to include documents from indices that do not contain a search field. diff --git a/docs/maps/trouble-shooting.asciidoc b/docs/maps/trouble-shooting.asciidoc index 76383f8953a78..cfc47cf6f0e4f 100644 --- a/docs/maps/trouble-shooting.asciidoc +++ b/docs/maps/trouble-shooting.asciidoc @@ -1,13 +1,18 @@ [role="xpack"] [[maps-troubleshooting]] -== Elastic Maps troubleshooting +== Troubleshoot Maps + +++++ +Troubleshoot +++++ + Use the information in this section to inspect Elasticsearch requests and find solutions to common problems. [float] === Inspect Elasticsearch requests -*Elastic Maps* uses the {ref}/search-search.html[{es} search API] to get documents and aggregation results from {es}. To troubleshoot these requests, open the Inspector, which shows the most recent requests for each layer. You can switch between different requests using the *Request* dropdown. +Maps uses the {ref}/search-search.html[{es} search API] to get documents and aggregation results from {es}. To troubleshoot these requests, open the Inspector, which shows the most recent requests for each layer. You can switch between different requests using the *Request* dropdown. [role="screenshot"] image::maps/images/inspector.png[] @@ -33,6 +38,6 @@ image::maps/images/inspector.png[] [float] ==== Coordinate and region map visualizations not available in New Visualization menu -Kibana’s out-of-the-box settings no longer offers coordinate and region maps as a choice in the New Visualization menu because you can create these maps in *Elastic Maps*. +Kibana’s out-of-the-box settings no longer offers coordinate and region maps as a +choice in the New Visualization menu because you can create these maps in the Maps app. If you want to create new coordinate and region map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. - diff --git a/docs/maps/vector-tooltips.asciidoc b/docs/maps/vector-tooltips.asciidoc index a3772272eeed5..a8eb6c20bae77 100644 --- a/docs/maps/vector-tooltips.asciidoc +++ b/docs/maps/vector-tooltips.asciidoc @@ -15,7 +15,7 @@ image::maps/images/multifeature_tooltip.png[] [float] [[maps-vector-tooltip-formatting]] -==== Formatting tooltips +==== Format tooltips You can format the attributes in a tooltip by adding <> to your Kibana index pattern. You can use field formatters to round numbers, provide units, @@ -23,7 +23,7 @@ and even display images in your tooltip. [float] [[maps-vector-tooltip-locking]] -==== Locking a tooltip at the current location +==== Lock a tooltip at the current location You can lock a tooltip in place by clicking a location on the map. With locked tooltips you can: diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 6d6996c2094c6..8c04167de1236 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -40,7 +40,7 @@ repeated production process, but rather for the initial exploration of your data === Upload geospatial data To visualize geospatial data in a point or shape file, you can upload it using the <> -feature in *Elastic Maps*, and then use that data as a layer in a map. +feature in Maps, and then use that data as a layer in a map. The data is also available for use in the broader Kibana ecosystem, for example, in visualizations and Canvas workpads. With GeoJSON Upload, you can upload a file up to 50 MB. diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index ab7a85a2ff851..0dee112d15e86 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -13,7 +13,7 @@ https://github.com/elastic/dockerfiles/tree/{branch}/kibana[GitHub]. These images are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +<> to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/deb.asciidoc b/docs/setup/install/deb.asciidoc index dfa1e3a37fd05..d24c1cf8ae9d1 100644 --- a/docs/setup/install/deb.asciidoc +++ b/docs/setup/install/deb.asciidoc @@ -10,7 +10,7 @@ Kibana on any Debian-based system such as Debian and Ubuntu. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +<> to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/rpm.asciidoc b/docs/setup/install/rpm.asciidoc index ccc38c2696158..5d4f47f300eac 100644 --- a/docs/setup/install/rpm.asciidoc +++ b/docs/setup/install/rpm.asciidoc @@ -15,7 +15,7 @@ such as SLES 11 and CentOS 5. Please see <> instead. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +<> to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/targz.asciidoc b/docs/setup/install/targz.asciidoc index c8bff5d58889d..14ee1b297ffc6 100644 --- a/docs/setup/install/targz.asciidoc +++ b/docs/setup/install/targz.asciidoc @@ -9,7 +9,7 @@ are the easiest formats to use when trying out Kibana. These packages are free to use under the Elastic license. They contain open source and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +<> to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/setup/install/windows.asciidoc b/docs/setup/install/windows.asciidoc index 24bf74f607fef..0d467f2fa7dd9 100644 --- a/docs/setup/install/windows.asciidoc +++ b/docs/setup/install/windows.asciidoc @@ -8,7 +8,7 @@ Kibana can be installed on Windows using the `.zip` package. This package is free to use under the Elastic license. It contains open source and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +<> to try out all of the paid commercial features. See the https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index bd6d10c3d7eb3..a812d4e3bdd2d 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -66,7 +66,7 @@ image:images/Dashboard_add_new_visualization.png[Example add new visualization t + For information on how to create visualizations, see <>. + -For information on how to create Maps, see <>. +For information on how to create maps, see <>. To add an existing element: diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 8b987f81779e3..6438098ad2c60 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -85,16 +85,16 @@ image::images/intro-dashboard.png[] * <> allows you to display your data in line charts, bar graphs, pie charts, histograms, and tables -(just to name a few). It's also home to *Lens*, the drag-and-drop interface. -*Visualize* supports the ability to add interactive +(just to name a few). It's also home to Lens, the drag-and-drop interface. +Visualize supports the ability to add interactive controls to your dashboard, and filter dashboard content in real time. * <> 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 people with a big-screen display. -* <> enables you to ask (and answer) meaningful -questions of your location-based data. *Elastic Maps* supports multiple +* <> 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. diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index f82bb0a406511..1bc74ce87de08 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -87,12 +87,14 @@ and {ml-docs}/xpack-ml.html[{ml-cap} {anomaly-detect}]. [[xpack-ml-dfanalytics]] == {dfanalytics-cap} +experimental[] + The Elastic {ml} {dfanalytics} feature enables you to analyze your data using -{oldetection} and {regression} algorithms and generate new indices that contain -the results alongside your source data. +{classification}, {oldetection}, and {regression} algorithms and generate new +indices that contain the results alongside your source data. If you have a license that includes the {ml-features}, you can create -{oldetection} {dfanalytics-jobs} and view their results on the *Analytics* page +{dfanalytics-jobs} and view their results on the *Analytics* page in {kib}. For example: [role="screenshot"] diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index f020f56774dab..6919b5a8772ef 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -38,7 +38,7 @@ Quickly build several types of basic visualizations by simply dragging and dropp data sets. <>:: -* *<>* — Displays geospatial data in {kib}. +* *<>* — Displays geospatial data in {kib}. * <>:: Display shaded cells within a matrix. diff --git a/docs/visualize/vega.asciidoc b/docs/visualize/vega.asciidoc index efe9094a14922..24bd3a44bebba 100644 --- a/docs/visualize/vega.asciidoc +++ b/docs/visualize/vega.asciidoc @@ -32,7 +32,7 @@ image::images/vega_lite_default.png[] The default visualization uses Vega-Lite version 2. To use Vega version 4, edit the `schema`. -Go to `$schema`, enter `https://vega.github.io/schema/vega/v4.json`, then click +Go to `$schema`, enter `https://vega.github.io/schema/vega/v5.json`, then click *Update*. [float] @@ -213,7 +213,7 @@ url: { format: {property: "features"} ---- -To enable Elastic Maps, the graph must specify `type=map` in the host +To enable Maps, the graph must specify `type=map` in the host configuration: [source,yaml] @@ -306,7 +306,7 @@ to your kibana.yml file. [[vega-useful-links]] === Resources and examples -experimental[] To learn more about Vega and Vega-List, refer to the resources and examples. +experimental[] To learn more about Vega and Vega-List, refer to the resources and examples. ==== Vega editor The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any diff --git a/examples/demo_search/kibana.json b/examples/demo_search/kibana.json index cdf74121ea2db..f909ca47fcd55 100644 --- a/examples/demo_search/kibana.json +++ b/examples/demo_search/kibana.json @@ -5,5 +5,6 @@ "server": true, "ui": true, "requiredPlugins": ["data"], - "optionalPlugins": [] + "optionalPlugins": [], + "extraPublicDirs": ["common"] } diff --git a/examples/demo_search/server/async_demo_search_strategy.ts b/examples/demo_search/server/async_demo_search_strategy.ts index 7ed5062acba48..2eda0f4d09e11 100644 --- a/examples/demo_search/server/async_demo_search_strategy.ts +++ b/examples/demo_search/server/async_demo_search_strategy.ts @@ -17,30 +17,32 @@ * under the License. */ -import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; -import { ASYNC_DEMO_SEARCH_STRATEGY } from '../common'; - -function getFibonacciSequence(n = 0) { - const beginning = [0, 1].slice(0, n); - return Array(Math.max(0, n)) - .fill(null) - .reduce((sequence, value, i) => { - if (i < 2) return sequence; - return [...sequence, sequence[i - 1] + sequence[i - 2]]; - }, beginning); -} - -const generateId = (() => { - let id = 0; - return () => `${id++}`; -})(); - -const loadedMap = new Map(); -const totalMap = new Map(); - -export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider = () => { +import { ISearchStrategy } from '../../../src/plugins/data/server'; +import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoRequest } from '../common'; + +export const asyncDemoSearchStrategyProvider = (): ISearchStrategy< + typeof ASYNC_DEMO_SEARCH_STRATEGY +> => { + function getFibonacciSequence(n = 0) { + const beginning = [0, 1].slice(0, n); + return Array(Math.max(0, n)) + .fill(null) + .reduce((sequence, value, i) => { + if (i < 2) return sequence; + return [...sequence, sequence[i - 1] + sequence[i - 2]]; + }, beginning); + } + + const generateId = (() => { + let id = 0; + return () => `${id++}`; + })(); + + const loadedMap = new Map(); + const totalMap = new Map(); + return { - search: async (request) => { + search: async (context, request: IAsyncDemoRequest) => { const id = request.id ?? generateId(); const loaded = (loadedMap.get(id) ?? 0) + 1; @@ -52,7 +54,7 @@ export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider { + cancel: async (context, id) => { loadedMap.delete(id); totalMap.delete(id); }, diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts index a1fd0e45dbc8e..36280ad22e83c 100644 --- a/examples/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -17,12 +17,12 @@ * under the License. */ -import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; -import { DEMO_SEARCH_STRATEGY } from '../common'; +import { ISearchStrategy } from '../../../src/plugins/data/server'; +import { DEMO_SEARCH_STRATEGY, IDemoRequest } from '../common'; -export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { +export const demoSearchStrategyProvider = (): ISearchStrategy => { return { - search: (request) => { + search: (context, request: IDemoRequest) => { return Promise.resolve({ greeting: request.mood === 'happy' diff --git a/examples/demo_search/server/index.ts b/examples/demo_search/server/index.ts index 6289b684b2b1e..368575b705c90 100644 --- a/examples/demo_search/server/index.ts +++ b/examples/demo_search/server/index.ts @@ -17,9 +17,6 @@ * under the License. */ -import { PluginInitializerContext, PluginInitializer } from 'kibana/server'; import { DemoDataPlugin } from './plugin'; -export const plugin: PluginInitializer = ( - initializerContext: PluginInitializerContext -) => new DemoDataPlugin(initializerContext); +export const plugin = () => new DemoDataPlugin(); diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts index 49fbae43e3aa2..8a72b5007f988 100644 --- a/examples/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; +import { Plugin, CoreSetup } from 'kibana/server'; import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; import { demoSearchStrategyProvider } from './demo_search_strategy'; import { @@ -56,18 +56,13 @@ declare module '../../../src/plugins/data/server' { } export class DemoDataPlugin implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} + constructor() {} public setup(core: CoreSetup, deps: IDemoSearchExplorerDeps) { - deps.data.search.registerSearchStrategyProvider( - this.initializerContext.opaqueId, - DEMO_SEARCH_STRATEGY, - demoSearchStrategyProvider - ); - deps.data.search.registerSearchStrategyProvider( - this.initializerContext.opaqueId, + deps.data.search.registerSearchStrategy(DEMO_SEARCH_STRATEGY, demoSearchStrategyProvider()); + deps.data.search.registerSearchStrategy( ASYNC_DEMO_SEARCH_STRATEGY, - asyncDemoSearchStrategyProvider + asyncDemoSearchStrategyProvider() ); } diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index 489f768552b28..486c6322fad93 100644 --- a/examples/embeddable_examples/kibana.json +++ b/examples/embeddable_examples/kibana.json @@ -5,5 +5,6 @@ "server": true, "ui": true, "requiredPlugins": ["embeddable"], - "optionalPlugins": [] + "optionalPlugins": [], + "extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"] } diff --git a/examples/search_explorer/public/search_api.tsx b/examples/search_explorer/public/search_api.tsx index c77ec725c6890..4a0e9afd20c85 100644 --- a/examples/search_explorer/public/search_api.tsx +++ b/examples/search_explorer/public/search_api.tsx @@ -23,11 +23,6 @@ import { GuideSection } from './guide_section'; import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search'; // @ts-ignore import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service'; - -// @ts-ignore -import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup'; -// @ts-ignore -import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search'; // @ts-ignore import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service'; @@ -54,14 +49,6 @@ export const SearchApiPage = () => ( description: 'search_service.ts', snippet: serverPlugin, }, - { - description: `i_search_setup.ts`, - snippet: serverSetupContract, - }, - { - description: 'i_search', - snippet: serverSearch, - }, ], }, ]} diff --git a/examples/url_generators_examples/kibana.json b/examples/url_generators_examples/kibana.json index cdb2127fdd26f..9658f5c7300aa 100644 --- a/examples/url_generators_examples/kibana.json +++ b/examples/url_generators_examples/kibana.json @@ -5,5 +5,8 @@ "server": false, "ui": true, "requiredPlugins": ["share"], - "optionalPlugins": [] + "optionalPlugins": [], + "extraPublicDirs": [ + "public/url_generator" + ] } diff --git a/package.json b/package.json index e8b07ae4abba2..06dfb4cdfe387 100644 --- a/package.json +++ b/package.json @@ -122,8 +122,8 @@ "@babel/core": "^7.10.2", "@babel/plugin-transform-modules-commonjs": "^7.10.1", "@babel/register": "^7.10.1", - "@elastic/apm-rum": "^5.1.1", - "@elastic/charts": "19.2.0", + "@elastic/apm-rum": "^5.2.0", + "@elastic/charts": "19.5.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.9.3", "@elastic/eui": "24.1.0", @@ -144,7 +144,6 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", - "@types/tar": "^4.0.3", "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", "accept": "3.0.2", @@ -243,7 +242,7 @@ "react-input-range": "^1.3.0", "react-markdown": "^4.3.1", "react-monaco-editor": "~0.27.0", - "react-redux": "^7.1.3", + "react-redux": "^7.2.0", "react-resize-detector": "^4.2.0", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", @@ -280,9 +279,9 @@ "uuid": "3.3.2", "val-loader": "^1.1.1", "validate-npm-package-name": "2.2.2", - "vega-lib": "4.3.0", - "vega-lite": "^2.6.0", - "vega-schema-url-parser": "1.0.0", + "vega": "^5.13.0", + "vega-lite": "^4.13.1", + "vega-schema-url-parser": "^1.1.0", "vega-tooltip": "^0.12.0", "vision": "^5.3.3", "webpack": "^4.41.5", @@ -375,7 +374,7 @@ "@types/react": "^16.9.36", "@types/react-dom": "^16.9.8", "@types/react-grid-layout": "^0.16.7", - "@types/react-redux": "^7.1.7", + "@types/react-redux": "^7.1.9", "@types/react-resize-detector": "^4.0.1", "@types/react-router": "^5.1.3", "@types/react-router-dom": "^5.1.3", @@ -390,9 +389,10 @@ "@types/styled-components": "^5.1.0", "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", + "@types/tar": "^4.0.3", + "@types/testing-library__dom": "^6.10.0", "@types/testing-library__react": "^9.1.2", "@types/testing-library__react-hooks": "^3.1.0", - "@types/testing-library__dom": "^6.10.0", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", @@ -486,12 +486,10 @@ "prettier": "^2.0.5", "proxyquire": "1.8.0", "react-popper-tooltip": "^2.10.1", - "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.7", "simple-git": "1.116.0", - "simplebar-react": "^2.1.0", "sinon": "^7.4.2", "strip-ansi": "^3.0.1", "supertest": "^3.1.0", @@ -509,4 +507,4 @@ "node": "10.21.0", "yarn": "^1.21.1" } -} \ No newline at end of file +} diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts index 2eb6c6cc5aac6..861ea0988692c 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts @@ -18,7 +18,7 @@ */ import { ToolingLog } from '../tooling_log'; -import { KbnClientRequester, ReqOptions } from './kbn_client_requester'; +import { KibanaConfig, KbnClientRequester, ReqOptions } from './kbn_client_requester'; import { KbnClientStatus } from './kbn_client_status'; import { KbnClientPlugins } from './kbn_client_plugins'; import { KbnClientVersion } from './kbn_client_version'; @@ -26,7 +26,7 @@ import { KbnClientSavedObjects } from './kbn_client_saved_objects'; import { KbnClientUiSettings, UiSettingValues } from './kbn_client_ui_settings'; export class KbnClient { - private readonly requester = new KbnClientRequester(this.log, this.kibanaUrls); + private readonly requester = new KbnClientRequester(this.log, this.kibanaConfig); readonly status = new KbnClientStatus(this.requester); readonly plugins = new KbnClientPlugins(this.status); readonly version = new KbnClientVersion(this.status); @@ -43,10 +43,10 @@ export class KbnClient { */ constructor( private readonly log: ToolingLog, - private readonly kibanaUrls: string[], + private readonly kibanaConfig: KibanaConfig, private readonly uiSettingDefaults?: UiSettingValues ) { - if (!kibanaUrls.length) { + if (!kibanaConfig.url) { throw new Error('missing Kibana urls'); } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts index ea4159de55749..2aba2be56f277 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - import Url from 'url'; - -import Axios from 'axios'; +import Https from 'https'; +import Axios, { AxiosResponse } from 'axios'; import { isAxiosRequestError, isAxiosResponseError } from '../axios'; import { ToolingLog } from '../tooling_log'; @@ -70,20 +69,38 @@ const delay = (ms: number) => setTimeout(resolve, ms); }); +export interface KibanaConfig { + url: string; + ssl?: { + enabled: boolean; + key: string; + certificate: string; + certificateAuthorities: string; + }; +} + export class KbnClientRequester { - constructor(private readonly log: ToolingLog, private readonly kibanaUrls: string[]) {} + private readonly httpsAgent: Https.Agent | null; + constructor(private readonly log: ToolingLog, private readonly kibanaConfig: KibanaConfig) { + this.httpsAgent = + kibanaConfig.ssl && kibanaConfig.ssl.enabled + ? new Https.Agent({ + cert: kibanaConfig.ssl.certificate, + key: kibanaConfig.ssl.key, + ca: kibanaConfig.ssl.certificateAuthorities, + }) + : null; + } private pickUrl() { - const url = this.kibanaUrls.shift()!; - this.kibanaUrls.push(url); - return url; + return this.kibanaConfig.url; } public resolveUrl(relativeUrl: string = '/') { return Url.resolve(this.pickUrl(), relativeUrl); } - async request(options: ReqOptions): Promise { + async request(options: ReqOptions): Promise> { const url = Url.resolve(this.pickUrl(), options.path); const description = options.description || `${options.method} ${url}`; let attempt = 0; @@ -93,7 +110,7 @@ export class KbnClientRequester { attempt += 1; try { - const response = await Axios.request({ + const response = await Axios.request({ method: options.method, url, data: options.body, @@ -101,9 +118,10 @@ export class KbnClientRequester { headers: { 'kbn-xsrf': 'kbn-client', }, + httpsAgent: this.httpsAgent, }); - return response.data; + return response; } catch (error) { const conflictOnGet = isConcliftOnGetError(error); const requestedRetries = options.retries !== undefined; diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts index e671061b34352..7334c6353debf 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts @@ -71,12 +71,13 @@ export class KbnClientSavedObjects { public async migrate() { this.log.debug('Migrating saved objects'); - return await this.requester.request({ + const { data } = await this.requester.request({ description: 'migrate saved objects', path: uriencode`/internal/saved_objects/_migrate`, method: 'POST', body: {}, }); + return data; } /** @@ -85,11 +86,12 @@ export class KbnClientSavedObjects { public async get>(options: GetOptions) { this.log.debug('Gettings saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'get saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'GET', }); + return data; } /** @@ -98,7 +100,7 @@ export class KbnClientSavedObjects { public async create>(options: IndexOptions) { this.log.debug('Creating saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'update saved object', path: options.id ? uriencode`/api/saved_objects/${options.type}/${options.id}` @@ -113,6 +115,7 @@ export class KbnClientSavedObjects { references: options.references, }, }); + return data; } /** @@ -121,7 +124,7 @@ export class KbnClientSavedObjects { public async update>(options: UpdateOptions) { this.log.debug('Updating saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'update saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, query: { @@ -134,6 +137,7 @@ export class KbnClientSavedObjects { references: options.references, }, }); + return data; } /** @@ -142,10 +146,12 @@ export class KbnClientSavedObjects { public async delete(options: GetOptions) { this.log.debug('Deleting saved object %s/%s', options); - return await this.requester.request({ + const { data } = await this.requester.request({ description: 'delete saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'DELETE', }); + + return data; } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts index 22baf4a330416..4f203e73620f3 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts @@ -52,10 +52,11 @@ export class KbnClientStatus { * Get the full server status */ async get() { - return await this.requester.request({ + const { data } = await this.requester.request({ method: 'GET', path: 'api/status', }); + return data; } /** diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts index dbfa87e70032b..6ee2d3bfe59b0 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts @@ -57,10 +57,11 @@ export class KbnClientUiSettings { * Unset a uiSetting */ async unset(setting: string) { - return await this.requester.request({ + const { data } = await this.requester.request({ path: uriencode`/api/kibana/settings/${setting}`, method: 'DELETE', }); + return data; } /** @@ -105,11 +106,11 @@ export class KbnClientUiSettings { } private async getAll() { - const resp = await this.requester.request({ + const { data } = await this.requester.request({ path: '/api/kibana/settings', method: 'GET', }); - return resp.settings; + return data.settings; } } diff --git a/packages/kbn-es/src/utils/native_realm.js b/packages/kbn-es/src/utils/native_realm.js index 573944a8cc6d0..5075cfb965c08 100644 --- a/packages/kbn-es/src/utils/native_realm.js +++ b/packages/kbn-es/src/utils/native_realm.js @@ -37,13 +37,8 @@ exports.NativeRealm = class NativeRealm { this._log = log; } - async setPassword(username, password = this._elasticPassword, { attempt = 1 } = {}) { - await this._autoRetry(async () => { - this._log.info( - (attempt > 1 ? `attempt ${attempt}: ` : '') + - `setting ${chalk.bold(username)} password to ${chalk.bold(password)}` - ); - + async setPassword(username, password = this._elasticPassword, retryOpts = {}) { + await this._autoRetry(retryOpts, async () => { try { await this._client.security.changePassword({ username, @@ -83,8 +78,8 @@ exports.NativeRealm = class NativeRealm { ); } - async getReservedUsers() { - return await this._autoRetry(async () => { + async getReservedUsers(retryOpts = {}) { + return await this._autoRetry(retryOpts, async () => { const resp = await this._client.security.getUser(); const usernames = Object.keys(resp.body).filter( (user) => resp.body[user].metadata._reserved === true @@ -98,9 +93,9 @@ exports.NativeRealm = class NativeRealm { }); } - async isSecurityEnabled() { + async isSecurityEnabled(retryOpts = {}) { try { - return await this._autoRetry(async () => { + return await this._autoRetry(retryOpts, async () => { const { body: { features }, } = await this._client.xpack.info({ categories: 'features' }); @@ -115,18 +110,25 @@ exports.NativeRealm = class NativeRealm { } } - async _autoRetry(fn, attempt = 1) { + async _autoRetry(opts, fn) { + const { attempt = 1, maxAttempts = 3 } = opts; + try { return await fn(attempt); } catch (error) { - if (attempt >= 3) { + if (attempt >= maxAttempts) { throw error; } const sec = 1.5 * attempt; this._log.warning(`assuming ES isn't initialized completely, trying again in ${sec} seconds`); await new Promise((resolve) => setTimeout(resolve, sec * 1000)); - return await this._autoRetry(fn, attempt + 1); + + const nextOpts = { + ...opts, + attempt: attempt + 1, + }; + return await this._autoRetry(nextOpts, fn); } } }; diff --git a/packages/kbn-es/src/utils/native_realm.test.js b/packages/kbn-es/src/utils/native_realm.test.js index 54732f7136fcc..cd124c97dfdd4 100644 --- a/packages/kbn-es/src/utils/native_realm.test.js +++ b/packages/kbn-es/src/utils/native_realm.test.js @@ -85,7 +85,7 @@ describe('isSecurityEnabled', () => { throw error; }); - expect(await nativeRealm.isSecurityEnabled()).toBe(false); + expect(await nativeRealm.isSecurityEnabled({ maxAttempts: 1 })).toBe(false); }); test('rejects if unexpected error is thrown', async () => { @@ -97,9 +97,9 @@ describe('isSecurityEnabled', () => { throw error; }); - await expect(nativeRealm.isSecurityEnabled()).rejects.toThrowErrorMatchingInlineSnapshot( - `"ResponseError"` - ); + await expect( + nativeRealm.isSecurityEnabled({ maxAttempts: 1 }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"ResponseError"`); }); }); @@ -226,7 +226,7 @@ describe('setPassword', () => { }); await expect( - nativeRealm.setPassword('kibana_system', 'foo') + nativeRealm.setPassword('kibana_system', 'foo', { maxAttempts: 1 }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"SomeError"`); }); }); diff --git a/packages/kbn-optimizer/README.md b/packages/kbn-optimizer/README.md index c7f50c6af8dfd..9ff0f56344274 100644 --- a/packages/kbn-optimizer/README.md +++ b/packages/kbn-optimizer/README.md @@ -30,6 +30,18 @@ Bundles built by the the optimizer include a cache file which describes the info When a bundle is determined to be up-to-date a worker is not started for the bundle. If running the optimizer with the `--dev/--watch` flag, then all the files referenced by cached bundles are watched for changes. Once a change is detected in any of the files referenced by the built bundle a worker is started. If a file is changed that is referenced by several bundles then workers will be started for each bundle, combining workers together to respect the worker limit. +## Bundle Refs + +In order to dramatically reduce the size of our bundles, and the time it takes to build them, bundles will "ref" other bundles being built at the same time. When the optimizer starts it creates a list of "refs" that could be had from the list of bundles being built. Each worker uses that list to determine which import statements in a bundle should be replaced with a runtime reference to the output of another bundle. + +At runtime the bundles share a set of entry points via the `__kbnBundles__` global. By default a plugin shares `public` so that other code can use relative imports to access that directory. To expose additional directories they must be listed in the plugin's kibana.json "extraPublicDirs" field. The directories listed there will **also** be exported from the plugins bundle so that any other plugin can import that directory. "common" is commonly in the list of "extraPublicDirs". + +> NOTE: We plan to replace the `extraPublicDirs` functionality soon with better mechanisms for statically sharing code between bundles. + +When a directory is listed in the "extraPublicDirs" it will always be included in the bundle so that other plugins have access to it. The worker building the bundle has no way of knowing whether another plugin is using the directory, so be careful of adding test code or unnecessary directories to that list. + +Any import in a bundle which resolves into another bundles "context" directory, ie `src/plugins/*`, must map explicitly to a "public dir" exported by that plugin. If the resolved import is not in the list of public dirs an error will be thrown and the optimizer will fail to build that bundle until the error is fixed. + ## API To run the optimizer from code, you can import the [`OptimizerConfig`][OptimizerConfig] class and [`runOptimizer`][Optimizer] function. Create an [`OptimizerConfig`][OptimizerConfig] instance by calling it's static `create()` method with some options, then pass it to the [`runOptimizer`][Optimizer] function. `runOptimizer()` returns an observable of update objects, which are summaries of the optimizer state plus an optional `event` property which describes the internal events occuring and may be of use. You can use the [`logOptimizerState()`][LogOptimizerState] helper to write the relevant bits of state to a tooling log or checkout it's implementation to see how the internal events like [`WorkerStdio`][ObserveWorker] and [`WorkerStarted`][ObserveWorker] are used. diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index a372b9e394b9a..c7bf1dd60985d 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -45,6 +45,7 @@ "terser-webpack-plugin": "^2.1.2", "tinymath": "1.2.1", "url-loader": "^2.2.0", + "val-loader": "^1.1.1", "watchpack": "^1.6.0", "webpack": "^4.41.5", "webpack-merge": "^4.2.2" diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts index 7ddd10f4a388f..c881a15eac5b5 100644 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts +++ b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts @@ -19,6 +19,6 @@ import './legacy/styles.scss'; import './index.scss'; -import { fooLibFn } from '../../foo/public/index'; +import { fooLibFn } from '../../foo/public'; export * from './lib'; export { fooLibFn }; diff --git a/packages/kbn-optimizer/src/common/bundle.test.ts b/packages/kbn-optimizer/src/common/bundle.test.ts index ec78a1bdf020e..b209bbca25ac4 100644 --- a/packages/kbn-optimizer/src/common/bundle.test.ts +++ b/packages/kbn-optimizer/src/common/bundle.test.ts @@ -23,7 +23,7 @@ jest.mock('fs'); const SPEC: BundleSpec = { contextDir: '/foo/bar', - entry: 'entry', + publicDirNames: ['public'], id: 'bar', outputDir: '/foo/bar/target', sourceRoot: '/foo', @@ -49,9 +49,11 @@ it('creates cache keys', () => { }, "spec": Object { "contextDir": "/foo/bar", - "entry": "entry", "id": "bar", "outputDir": "/foo/bar/target", + "publicDirNames": Array [ + "public", + ], "sourceRoot": "/foo", "type": "plugin", }, @@ -82,9 +84,11 @@ it('parses bundles from JSON specs', () => { "state": undefined, }, "contextDir": "/foo/bar", - "entry": "entry", "id": "bar", "outputDir": "/foo/bar/target", + "publicDirNames": Array [ + "public", + ], "sourceRoot": "/foo", "type": "plugin", }, diff --git a/packages/kbn-optimizer/src/common/bundle.ts b/packages/kbn-optimizer/src/common/bundle.ts index 9e2ad186ba40c..80af94c30f8da 100644 --- a/packages/kbn-optimizer/src/common/bundle.ts +++ b/packages/kbn-optimizer/src/common/bundle.ts @@ -29,8 +29,8 @@ export interface BundleSpec { readonly type: typeof VALID_BUNDLE_TYPES[0]; /** Unique id for this bundle */ readonly id: string; - /** Webpack entry request for this plugin, relative to the contextDir */ - readonly entry: string; + /** directory names relative to the contextDir that can be imported from */ + readonly publicDirNames: string[]; /** Absolute path to the plugin source directory */ readonly contextDir: string; /** Absolute path to the root of the repository */ @@ -44,8 +44,8 @@ export class Bundle { public readonly type: BundleSpec['type']; /** Unique identifier for this bundle */ public readonly id: BundleSpec['id']; - /** Path, relative to `contextDir`, to the entry file for the Webpack bundle */ - public readonly entry: BundleSpec['entry']; + /** directory names relative to the contextDir that can be imported from */ + public readonly publicDirNames: BundleSpec['publicDirNames']; /** * Absolute path to the root of the bundle context (plugin directory) * where the entry is resolved relative to and the default output paths @@ -62,7 +62,7 @@ export class Bundle { constructor(spec: BundleSpec) { this.type = spec.type; this.id = spec.id; - this.entry = spec.entry; + this.publicDirNames = spec.publicDirNames; this.contextDir = spec.contextDir; this.sourceRoot = spec.sourceRoot; this.outputDir = spec.outputDir; @@ -73,8 +73,6 @@ export class Bundle { /** * Calculate the cache key for this bundle based from current * mtime values. - * - * @param mtimes pre-fetched mtimes (ms || undefined) for all referenced files */ createCacheKey(files: string[], mtimes: Map): unknown { return { @@ -94,7 +92,7 @@ export class Bundle { return { type: this.type, id: this.id, - entry: this.entry, + publicDirNames: this.publicDirNames, contextDir: this.contextDir, sourceRoot: this.sourceRoot, outputDir: this.outputDir, @@ -134,9 +132,9 @@ export function parseBundles(json: string) { throw new Error('`bundles[]` must have a string `id` property'); } - const { entry } = spec; - if (!(typeof entry === 'string')) { - throw new Error('`bundles[]` must have a string `entry` property'); + const { publicDirNames } = spec; + if (!Array.isArray(publicDirNames) || !publicDirNames.every((d) => typeof d === 'string')) { + throw new Error('`bundles[]` must have an array of strings `publicDirNames` property'); } const { contextDir } = spec; @@ -157,7 +155,7 @@ export function parseBundles(json: string) { return new Bundle({ type, id, - entry, + publicDirNames, contextDir, sourceRoot, outputDir, diff --git a/packages/kbn-optimizer/src/common/bundle_cache.ts b/packages/kbn-optimizer/src/common/bundle_cache.ts index 1dbc7f1d1b6b0..5ae3e4c28a201 100644 --- a/packages/kbn-optimizer/src/common/bundle_cache.ts +++ b/packages/kbn-optimizer/src/common/bundle_cache.ts @@ -25,6 +25,7 @@ export interface State { cacheKey?: unknown; moduleCount?: number; files?: string[]; + bundleRefExportIds?: string[]; } const DEFAULT_STATE: State = {}; @@ -87,6 +88,10 @@ export class BundleCache { return this.get().files; } + public getBundleRefExportIds() { + return this.get().bundleRefExportIds; + } + public getCacheKey() { return this.get().cacheKey; } diff --git a/packages/kbn-optimizer/src/common/bundle_refs.ts b/packages/kbn-optimizer/src/common/bundle_refs.ts new file mode 100644 index 0000000000000..a5c60f2031c0b --- /dev/null +++ b/packages/kbn-optimizer/src/common/bundle_refs.ts @@ -0,0 +1,130 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Path from 'path'; + +import { Bundle } from './bundle'; +import { UnknownVals } from './ts_helpers'; + +export interface BundleRef { + bundleId: string; + contextDir: string; + contextPrefix: string; + entry: string; + exportId: string; +} + +export class BundleRefs { + static fromBundles(bundles: Bundle[]) { + return new BundleRefs( + bundles.reduce( + (acc: BundleRef[], b) => [ + ...acc, + ...b.publicDirNames.map( + (name): BundleRef => ({ + bundleId: b.id, + contextDir: b.contextDir, + // Path.resolve converts separators and strips the final separator + contextPrefix: Path.resolve(b.contextDir) + Path.sep, + entry: name, + exportId: `${b.type}/${b.id}/${name}`, + }) + ), + ], + [] + ) + ); + } + + static parseSpec(json: unknown) { + if (typeof json !== 'string') { + throw new Error('expected `bundleRefs` spec to be a JSON string'); + } + + let spec; + try { + spec = JSON.parse(json); + } catch (error) { + throw new Error('`bundleRefs` spec must be valid JSON'); + } + + if (!Array.isArray(spec)) { + throw new Error('`bundleRefs` spec must be an array'); + } + + return new BundleRefs( + spec.map( + (refSpec: UnknownVals): BundleRef => { + if (typeof refSpec !== 'object' || !refSpec) { + throw new Error('`bundleRefs[]` must be an object'); + } + + const { bundleId } = refSpec; + if (typeof bundleId !== 'string') { + throw new Error('`bundleRefs[].bundleId` must be a string'); + } + + const { contextDir } = refSpec; + if (typeof contextDir !== 'string' || !Path.isAbsolute(contextDir)) { + throw new Error('`bundleRefs[].contextDir` must be an absolute directory'); + } + + const { contextPrefix } = refSpec; + if (typeof contextPrefix !== 'string' || !Path.isAbsolute(contextPrefix)) { + throw new Error('`bundleRefs[].contextPrefix` must be an absolute directory'); + } + + const { entry } = refSpec; + if (typeof entry !== 'string') { + throw new Error('`bundleRefs[].entry` must be a string'); + } + + const { exportId } = refSpec; + if (typeof exportId !== 'string') { + throw new Error('`bundleRefs[].exportId` must be a string'); + } + + return { + bundleId, + contextDir, + contextPrefix, + entry, + exportId, + }; + } + ) + ); + } + + constructor(private readonly refs: BundleRef[]) {} + + public filterByExportIds(exportIds: string[]) { + return this.refs.filter((r) => exportIds.includes(r.exportId)); + } + + public filterByContextPrefix(bundle: Bundle, absolutePath: string) { + return this.refs.filter( + (ref) => ref.bundleId !== bundle.id && absolutePath.startsWith(ref.contextPrefix) + ); + } + + public toSpecJson() { + return JSON.stringify(this.refs); + } +} diff --git a/packages/kbn-optimizer/src/common/index.ts b/packages/kbn-optimizer/src/common/index.ts index c51905be04565..7d021a5ee7847 100644 --- a/packages/kbn-optimizer/src/common/index.ts +++ b/packages/kbn-optimizer/src/common/index.ts @@ -19,6 +19,7 @@ export * from './bundle'; export * from './bundle_cache'; +export * from './bundle_refs'; export * from './worker_config'; export * from './worker_messages'; export * from './compiler_messages'; diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 2814ab32017d2..2265bad9f6afa 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -9,9 +9,11 @@ OptimizerConfig { "state": undefined, }, "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "entry": "./public/index", "id": "bar", "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public, + "publicDirNames": Array [ + "public", + ], "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, "type": "plugin", }, @@ -21,9 +23,11 @@ OptimizerConfig { "state": undefined, }, "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "entry": "./public/index", "id": "foo", "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public, + "publicDirNames": Array [ + "public", + ], "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, "type": "plugin", }, @@ -35,16 +39,19 @@ OptimizerConfig { "plugins": Array [ Object { "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, + "extraPublicDirs": Array [], "id": "bar", "isUiPlugin": true, }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/baz, + "extraPublicDirs": Array [], "id": "baz", "isUiPlugin": false, }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, + "extraPublicDirs": Array [], "id": "foo", "isUiPlugin": true, }, @@ -55,8 +62,8 @@ OptimizerConfig { } `; -exports[`prepares assets for distribution: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i { const foo = config.bundles.find((b) => b.id === 'foo')!; expect(foo).toBeTruthy(); foo.cache.refresh(); - expect(foo.cache.getModuleCount()).toBe(5); + expect(foo.cache.getModuleCount()).toBe(6); expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, + /packages/kbn-optimizer/target/worker/entry_point_creator.js, /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -148,7 +149,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { bar.cache.refresh(); expect(bar.cache.getModuleCount()).toBe( // code + styles + style/css-loader runtimes + public path updater - 21 + 18 ); expect(bar.cache.getReferencedFiles()).toMatchInlineSnapshot(` @@ -159,11 +160,8 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/legacy/styles.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/lib.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/legacy/ui/public/icon.svg, + /packages/kbn-optimizer/target/worker/entry_point_creator.js, /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); diff --git a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts index 39064c64062e8..48cab508954a0 100644 --- a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts @@ -75,6 +75,7 @@ it('emits "bundle cached" event when everything is updated', async () => { optimizerCacheKey, files, moduleCount: files.length, + bundleRefExportIds: [], }); const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) @@ -115,6 +116,7 @@ it('emits "bundle not cached" event when cacheKey is up to date but caching is d optimizerCacheKey, files, moduleCount: files.length, + bundleRefExportIds: [], }); const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) @@ -155,6 +157,7 @@ it('emits "bundle not cached" event when optimizerCacheKey is missing', async () optimizerCacheKey: undefined, files, moduleCount: files.length, + bundleRefExportIds: [], }); const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) @@ -195,6 +198,7 @@ it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes optimizerCacheKey: 'old', files, moduleCount: files.length, + bundleRefExportIds: [], }); const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) @@ -217,6 +221,53 @@ it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes `); }); +it('emits "bundle not cached" event when bundleRefExportIds is outdated, includes diff', async () => { + const config = OptimizerConfig.create({ + repoRoot: MOCK_REPO_DIR, + pluginScanDirs: [], + pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], + maxWorkerCount: 1, + }); + const [bundle] = config.bundles; + + const optimizerCacheKey = 'optimizerCacheKey'; + const files = [ + Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), + Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), + Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), + ]; + const mtimes = await getMtimes(files); + const cacheKey = bundle.createCacheKey(files, mtimes); + + bundle.cache.set({ + cacheKey, + optimizerCacheKey, + files, + moduleCount: files.length, + bundleRefExportIds: ['plugin/bar/public'], + }); + + const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) + .pipe(toArray()) + .toPromise(); + + expect(cacheEvents).toMatchInlineSnapshot(` + Array [ + Object { + "bundle": , + "diff": "- Expected + + Received + +  [ + + \\"plugin/bar/public\\" +  ]", + "reason": "bundle references outdated", + "type": "bundle not cached", + }, + ] + `); +}); + it('emits "bundle not cached" event when cacheKey is missing', async () => { const config = OptimizerConfig.create({ repoRoot: MOCK_REPO_DIR, @@ -238,6 +289,7 @@ it('emits "bundle not cached" event when cacheKey is missing', async () => { optimizerCacheKey, files, moduleCount: files.length, + bundleRefExportIds: [], }); const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) @@ -276,6 +328,7 @@ it('emits "bundle not cached" event when cacheKey is outdated', async () => { optimizerCacheKey, files, moduleCount: files.length, + bundleRefExportIds: [], }); jest.spyOn(bundle, 'createCacheKey').mockImplementation(() => 'new'); diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts index 91d0f308e0ef6..176b17c979da9 100644 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts @@ -29,14 +29,14 @@ jest.mock('fs'); jest.mock('watchpack'); const MockWatchPack: jest.MockedClass = jest.requireMock('watchpack'); -const bundleEntryPath = (bundle: Bundle) => `${bundle.contextDir}/${bundle.entry}`; +const bundleEntryPath = (bundle: Bundle) => `${bundle.contextDir}/public/index.ts`; const makeTestBundle = (id: string) => { const bundle = new Bundle({ type: 'plugin', id, contextDir: `/repo/plugins/${id}/public`, - entry: 'index.ts', + publicDirNames: ['public'], outputDir: `/repo/plugins/${id}/target/public`, sourceRoot: `/repo`, }); diff --git a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts index 4671276797049..ca50a49e26913 100644 --- a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts +++ b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts @@ -57,7 +57,7 @@ const assertReturnVal = (workers: Assignments[]) => { const testBundle = (id: string) => new Bundle({ contextDir: `/repo/plugin/${id}/public`, - entry: 'index.ts', + publicDirNames: ['public'], id, outputDir: `/repo/plugins/${id}/target/public`, sourceRoot: `/repo`, diff --git a/packages/kbn-optimizer/src/optimizer/bundle_cache.ts b/packages/kbn-optimizer/src/optimizer/bundle_cache.ts index 55e8e1d3fd084..83db8570bd408 100644 --- a/packages/kbn-optimizer/src/optimizer/bundle_cache.ts +++ b/packages/kbn-optimizer/src/optimizer/bundle_cache.ts @@ -20,7 +20,7 @@ import * as Rx from 'rxjs'; import { mergeAll } from 'rxjs/operators'; -import { Bundle } from '../common'; +import { Bundle, BundleRefs } from '../common'; import { OptimizerConfig } from './optimizer_config'; import { getMtimes } from './get_mtimes'; @@ -35,7 +35,9 @@ export interface BundleNotCachedEvent { | 'optimizer cache key mismatch' | 'missing cache key' | 'cache key mismatch' - | 'cache disabled'; + | 'cache disabled' + | 'bundle references missing' + | 'bundle references outdated'; diff?: string; bundle: Bundle; } @@ -52,6 +54,7 @@ export function getBundleCacheEvent$( return Rx.defer(async () => { const events: BundleCacheEvent[] = []; const eligibleBundles: Bundle[] = []; + const bundleRefs = BundleRefs.fromBundles(config.bundles); for (const bundle of config.bundles) { if (!config.cache) { @@ -93,6 +96,32 @@ export function getBundleCacheEvent$( continue; } + const bundleRefExportIds = bundle.cache.getBundleRefExportIds(); + if (!bundleRefExportIds) { + events.push({ + type: 'bundle not cached', + reason: 'bundle references missing', + bundle, + }); + continue; + } + + const refs = bundleRefs.filterByExportIds(bundleRefExportIds); + + const bundleRefsDiff = diffCacheKey( + refs.map((r) => r.exportId).sort((a, b) => a.localeCompare(b)), + bundleRefExportIds + ); + if (bundleRefsDiff) { + events.push({ + type: 'bundle not cached', + reason: 'bundle references outdated', + diff: bundleRefsDiff, + bundle, + }); + continue; + } + eligibleBundles.push(bundle); } diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.ts index 2766f6d63702b..d0aaad979485d 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.ts @@ -37,16 +37,6 @@ import { OptimizerConfig } from './optimizer_config'; const OPTIMIZER_DIR = Path.dirname(require.resolve('../../package.json')); const RELATIVE_DIR = Path.relative(REPO_ROOT, OPTIMIZER_DIR); -function omit(obj: T, keys: K[]): Omit { - const result: any = {}; - for (const [key, value] of Object.entries(obj) as any) { - if (!keys.includes(key)) { - result[key] = value; - } - } - return result as Omit; -} - export function diffCacheKey(expected?: unknown, actual?: unknown) { const expectedJson = jsonStable(expected, { space: ' ', @@ -185,7 +175,7 @@ export async function getOptimizerCacheKey(config: OptimizerConfig) { bootstrap, deletedPaths, modifiedTimes: {} as Record, - workerConfig: omit(config.getWorkerConfig('♻'), ['watch', 'profileWebpack', 'cache']), + workerConfig: config.getCacheableWorkerConfig(), }; const mtimes = await getMtimes(modifiedPaths); diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts index 2174c488ad6cc..bbd3ddc11f448 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts @@ -31,16 +31,19 @@ it('returns a bundle for core and each plugin', () => { directory: '/repo/plugins/foo', id: 'foo', isUiPlugin: true, + extraPublicDirs: [], }, { directory: '/repo/plugins/bar', id: 'bar', isUiPlugin: false, + extraPublicDirs: [], }, { directory: '/outside/of/repo/plugins/baz', id: 'baz', isUiPlugin: true, + extraPublicDirs: [], }, ], '/repo' @@ -49,17 +52,21 @@ it('returns a bundle for core and each plugin', () => { Array [ Object { "contextDir": /plugins/foo, - "entry": "./public/index", "id": "foo", "outputDir": /plugins/foo/target/public, + "publicDirNames": Array [ + "public", + ], "sourceRoot": , "type": "plugin", }, Object { "contextDir": "/outside/of/repo/plugins/baz", - "entry": "./public/index", "id": "baz", "outputDir": "/outside/of/repo/plugins/baz/target/public", + "publicDirNames": Array [ + "public", + ], "sourceRoot": , "type": "plugin", }, diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts index b75a8a6edc264..2635289088725 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts @@ -31,7 +31,7 @@ export function getPluginBundles(plugins: KibanaPlatformPlugin[], repoRoot: stri new Bundle({ type: 'plugin', id: p.id, - entry: './public/index', + publicDirNames: ['public', ...p.extraPublicDirs], sourceRoot: repoRoot, contextDir: p.directory, outputDir: Path.resolve(p.directory, 'target/public'), diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts index e047b6d1e44cf..0961881df461c 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts @@ -37,21 +37,25 @@ it('parses kibana.json files of plugins found in pluginDirs', () => { Array [ Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar, + "extraPublicDirs": Array [], "id": "bar", "isUiPlugin": true, }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz, + "extraPublicDirs": Array [], "id": "baz", "isUiPlugin": false, }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo, + "extraPublicDirs": Array [], "id": "foo", "isUiPlugin": true, }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz, + "extraPublicDirs": Array [], "id": "test_baz", "isUiPlugin": false, }, diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts index 992feab6cd364..bfc60a29efa27 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts @@ -26,6 +26,7 @@ export interface KibanaPlatformPlugin { readonly directory: string; readonly id: string; readonly isUiPlugin: boolean; + readonly extraPublicDirs: string[]; } /** @@ -64,9 +65,24 @@ function readKibanaPlatformPlugin(manifestPath: string): KibanaPlatformPlugin { throw new TypeError('expected new platform plugin manifest to have a string id'); } + let extraPublicDirs: string[] | undefined; + if (manifest.extraPublicDirs) { + if ( + !Array.isArray(manifest.extraPublicDirs) || + !manifest.extraPublicDirs.every((p) => typeof p === 'string') + ) { + throw new TypeError( + 'expected new platform plugin manifest to have an array of strings `extraPublicDirs` property' + ); + } + + extraPublicDirs = manifest.extraPublicDirs as string[]; + } + return { directory: Path.dirname(manifestPath), id: manifest.id, isUiPlugin: !!manifest.ui, + extraPublicDirs: extraPublicDirs || [], }; } diff --git a/packages/kbn-optimizer/src/optimizer/observe_worker.ts b/packages/kbn-optimizer/src/optimizer/observe_worker.ts index c929cf62d1bb0..fef3efc13a516 100644 --- a/packages/kbn-optimizer/src/optimizer/observe_worker.ts +++ b/packages/kbn-optimizer/src/optimizer/observe_worker.ts @@ -22,9 +22,9 @@ import { inspect } from 'util'; import execa from 'execa'; import * as Rx from 'rxjs'; -import { map, takeUntil } from 'rxjs/operators'; +import { map, takeUntil, first, ignoreElements } from 'rxjs/operators'; -import { isWorkerMsg, WorkerConfig, WorkerMsg, Bundle } from '../common'; +import { isWorkerMsg, WorkerConfig, WorkerMsg, Bundle, BundleRefs } from '../common'; import { OptimizerConfig } from './optimizer_config'; @@ -68,15 +68,11 @@ if (inspectFlagIndex !== -1) { function usingWorkerProc( config: OptimizerConfig, - workerConfig: WorkerConfig, - bundles: Bundle[], fn: (proc: execa.ExecaChildProcess) => Rx.Observable ) { return Rx.using( (): ProcResource => { - const args = [JSON.stringify(workerConfig), JSON.stringify(bundles.map((b) => b.toSpec()))]; - - const proc = execa.node(require.resolve('../worker/run_worker'), args, { + const proc = execa.node(require.resolve('../worker/run_worker'), [], { nodeOptions: [ ...(inspectFlag && config.inspectWorkers ? [`${inspectFlag}=${inspectPortCounter++}`] @@ -125,6 +121,51 @@ function observeStdio$(stream: Readable, name: WorkerStdio['stream']) { ); } +/** + * We used to pass configuration to the worker as JSON encoded arguments, but they + * grew too large for argv, especially on Windows, so we had to move to an async init + * where we send the args over IPC. To keep the logic simple we basically mock the + * argv behavior and don't use complicated messages or anything so that state can + * be initialized in the worker before most of the code is run. + */ +function initWorker( + proc: execa.ExecaChildProcess, + config: OptimizerConfig, + workerConfig: WorkerConfig, + bundles: Bundle[] +) { + const msg$ = Rx.fromEvent<[unknown]>(proc, 'message').pipe( + // validate the initialization messages from the process + map(([msg]) => { + if (typeof msg === 'string') { + switch (msg) { + case 'init': + return 'init' as const; + case 'ready': + return 'ready' as const; + } + } + + throw new Error(`unexpected message from worker while initializing: [${inspect(msg)}]`); + }) + ); + + return Rx.concat( + msg$.pipe(first((msg) => msg === 'init')), + Rx.defer(() => { + proc.send({ + args: [ + JSON.stringify(workerConfig), + JSON.stringify(bundles.map((b) => b.toSpec())), + BundleRefs.fromBundles(config.bundles).toSpecJson(), + ], + }); + return []; + }), + msg$.pipe(first((msg) => msg === 'ready')) + ).pipe(ignoreElements()); +} + /** * Start a worker process with the specified `workerConfig` and * `bundles` and return an observable of the events related to @@ -136,10 +177,11 @@ export function observeWorker( workerConfig: WorkerConfig, bundles: Bundle[] ): Rx.Observable { - return usingWorkerProc(config, workerConfig, bundles, (proc) => { - let lastMsg: WorkerMsg; + return usingWorkerProc(config, (proc) => { + const init$ = initWorker(proc, config, workerConfig, bundles); - return Rx.merge( + let lastMsg: WorkerMsg; + const worker$: Rx.Observable = Rx.merge( Rx.of({ type: 'worker started', bundles, @@ -197,5 +239,7 @@ export function observeWorker( ) ) ); + + return Rx.concat(init$, worker$); }); } diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 37d8a4f5eb8ae..c9e9b3ad01ccc 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -20,7 +20,7 @@ import Path from 'path'; import Os from 'os'; -import { Bundle, WorkerConfig } from '../common'; +import { Bundle, WorkerConfig, CacheableWorkerConfig } from '../common'; import { findKibanaPlatformPlugins, KibanaPlatformPlugin } from './kibana_platform_plugins'; import { getPluginBundles } from './get_plugin_bundles'; @@ -34,6 +34,16 @@ function pickMaxWorkerCount(dist: boolean) { return Math.max(maxWorkers, 2); } +function omit(obj: T, keys: K[]): Omit { + const result: any = {}; + for (const [key, value] of Object.entries(obj) as any) { + if (!keys.includes(key)) { + result[key] = value; + } + } + return result as Omit; +} + interface Options { /** absolute path to root of the repo/build */ repoRoot: string; @@ -152,7 +162,7 @@ export class OptimizerConfig { new Bundle({ type: 'entry', id: 'core', - entry: './public/index', + publicDirNames: ['public', 'public/utils'], sourceRoot: options.repoRoot, contextDir: Path.resolve(options.repoRoot, 'src/core'), outputDir: Path.resolve(options.repoRoot, 'src/core/target/public'), @@ -198,4 +208,14 @@ export class OptimizerConfig { browserslistEnv: this.dist ? 'production' : process.env.BROWSERSLIST_ENV || 'dev', }; } + + getCacheableWorkerConfig(): CacheableWorkerConfig { + return omit(this.getWorkerConfig('♻'), [ + // these config options don't change the output of the bundles, so + // should not invalidate caches when they change + 'watch', + 'profileWebpack', + 'cache', + ]); + } } diff --git a/packages/kbn-optimizer/src/worker/bundle_ref_module.ts b/packages/kbn-optimizer/src/worker/bundle_ref_module.ts new file mode 100644 index 0000000000000..cde25564cf528 --- /dev/null +++ b/packages/kbn-optimizer/src/worker/bundle_ref_module.ts @@ -0,0 +1,67 @@ +/* eslint-disable @kbn/eslint/require-license-header */ + +/** + * @notice + * + * This module was heavily inspired by the externals plugin that ships with webpack@97d58d31 + * MIT License http://www.opensource.org/licenses/mit-license.php + * Author Tobias Koppers @sokra + */ + +// @ts-ignore not typed by @types/webpack +import Module from 'webpack/lib/Module'; + +export class BundleRefModule extends Module { + public built = false; + public buildMeta?: any; + public buildInfo?: any; + public exportsArgument = '__webpack_exports__'; + + constructor(public readonly exportId: string) { + super('kbn/bundleRef', null); + } + + libIdent() { + return this.exportId; + } + + chunkCondition(chunk: any) { + return chunk.hasEntryModule(); + } + + identifier() { + return '@kbn/bundleRef ' + JSON.stringify(this.exportId); + } + + readableIdentifier() { + return this.identifier(); + } + + needRebuild() { + return false; + } + + build(_: any, __: any, ___: any, ____: any, callback: () => void) { + this.built = true; + this.buildMeta = {}; + this.buildInfo = {}; + callback(); + } + + source() { + return ` + __webpack_require__.r(__webpack_exports__); + var ns = __kbnBundles__.get('${this.exportId}'); + Object.defineProperties(__webpack_exports__, Object.getOwnPropertyDescriptors(ns)) + `; + } + + size() { + return 42; + } + + updateHash(hash: any) { + hash.update(this.identifier()); + super.updateHash(hash); + } +} diff --git a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts new file mode 100644 index 0000000000000..9c4d5ed7f8a98 --- /dev/null +++ b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts @@ -0,0 +1,192 @@ +/* eslint-disable @kbn/eslint/require-license-header */ + +/** + * @notice + * + * This module was heavily inspired by the externals plugin that ships with webpack@97d58d31 + * MIT License http://www.opensource.org/licenses/mit-license.php + * Author Tobias Koppers @sokra + */ + +import Path from 'path'; +import Fs from 'fs'; + +import webpack from 'webpack'; + +import { Bundle, BundleRefs, BundleRef } from '../common'; +import { BundleRefModule } from './bundle_ref_module'; + +const RESOLVE_EXTENSIONS = ['.js', '.ts', '.tsx']; + +function safeStat(path: string): Promise { + return new Promise((resolve, reject) => { + Fs.stat(path, (error, stat) => { + if (error?.code === 'ENOENT') { + resolve(undefined); + } else if (error) { + reject(error); + } else { + resolve(stat); + } + }); + }); +} + +interface RequestData { + context: string; + dependencies: Array<{ request: string }>; +} + +type Callback = (error?: any, result?: T) => void; +type ModuleFactory = (data: RequestData, callback: Callback) => void; + +export class BundleRefsPlugin { + private readonly resolvedRefEntryCache = new Map>(); + private readonly resolvedRequestCache = new Map>(); + private readonly ignorePrefix = Path.resolve(this.bundle.contextDir) + Path.sep; + + constructor(private readonly bundle: Bundle, private readonly bundleRefs: BundleRefs) {} + + /** + * Called by webpack when the plugin is passed in the webpack config + */ + public apply(compiler: webpack.Compiler) { + // called whenever the compiler starts to compile, passed the params + // that will be used to create the compilation + compiler.hooks.compile.tap('BundleRefsPlugin', (compilationParams: any) => { + // clear caches because a new compilation is starting, meaning that files have + // changed and we should re-run resolutions + this.resolvedRefEntryCache.clear(); + this.resolvedRequestCache.clear(); + + // hook into the creation of NormalModule instances in webpack, if the import + // statement leading to the creation of the module is pointing to a bundleRef + // entry then create a BundleRefModule instead of a NormalModule. + compilationParams.normalModuleFactory.hooks.factory.tap( + 'BundleRefsPlugin/normalModuleFactory/factory', + (wrappedFactory: ModuleFactory): ModuleFactory => (data, callback) => { + const context = data.context; + const dep = data.dependencies[0]; + + this.maybeReplaceImport(context, dep.request).then( + (module) => { + if (!module) { + wrappedFactory(data, callback); + } else { + callback(undefined, module); + } + }, + (error) => callback(error) + ); + } + ); + }); + } + + private cachedResolveRefEntry(ref: BundleRef) { + const cached = this.resolvedRefEntryCache.get(ref); + + if (cached) { + return cached; + } + + const absoluteRequest = Path.resolve(ref.contextDir, ref.entry); + const promise = this.cachedResolveRequest(absoluteRequest).then((resolved) => { + if (!resolved) { + throw new Error(`Unable to resolve request [${ref.entry}] relative to [${ref.contextDir}]`); + } + + return resolved; + }); + this.resolvedRefEntryCache.set(ref, promise); + return promise; + } + + private cachedResolveRequest(absoluteRequest: string) { + const cached = this.resolvedRequestCache.get(absoluteRequest); + + if (cached) { + return cached; + } + + const promise = this.resolveRequest(absoluteRequest); + this.resolvedRequestCache.set(absoluteRequest, promise); + return promise; + } + + private async resolveRequest(absoluteRequest: string) { + const stats = await safeStat(absoluteRequest); + if (stats && stats.isFile()) { + return absoluteRequest; + } + + // look for an index file in directories + if (stats?.isDirectory()) { + for (const ext of RESOLVE_EXTENSIONS) { + const indexPath = Path.resolve(absoluteRequest, `index${ext}`); + const indexStats = await safeStat(indexPath); + if (indexStats?.isFile()) { + return indexPath; + } + } + } + + // look for a file with one of the supported extensions + for (const ext of RESOLVE_EXTENSIONS) { + const filePath = `${absoluteRequest}${ext}`; + const fileStats = await safeStat(filePath); + if (fileStats?.isFile()) { + return filePath; + } + } + + return; + } + + /** + * Determine if an import request resolves to a bundleRef export id. If the + * request resolves to a bundle ref context but none of the exported directories + * then an error is thrown. If the request does not resolve to a bundleRef then + * undefined is returned. Otherwise it returns the referenced bundleRef. + */ + private async maybeReplaceImport(context: string, request: string) { + // ignore imports that have loaders defined or are not relative seeming + if (request.includes('!') || !request.startsWith('.')) { + return; + } + + const requestExt = Path.extname(request); + if (requestExt && !RESOLVE_EXTENSIONS.includes(requestExt)) { + return; + } + + const absoluteRequest = Path.resolve(context, request); + if (absoluteRequest.startsWith(this.ignorePrefix)) { + return; + } + + const resolved = await this.cachedResolveRequest(absoluteRequest); + if (!resolved) { + return; + } + + const eligibleRefs = this.bundleRefs.filterByContextPrefix(this.bundle, resolved); + if (!eligibleRefs.length) { + // import doesn't match a bundle context + return; + } + + for (const ref of eligibleRefs) { + const resolvedEntry = await this.cachedResolveRefEntry(ref); + if (resolved === resolvedEntry) { + return new BundleRefModule(ref.exportId); + } + } + + const bundleId = Array.from(new Set(eligibleRefs.map((r) => r.bundleId))).join(', '); + const publicDir = eligibleRefs.map((r) => r.entry).join(', '); + throw new Error( + `import [${request}] references a non-public export of the [${bundleId}] bundle and must point to one of the public directories: [${publicDir}]` + ); + } +} diff --git a/src/plugins/data/server/search/i_search_setup.ts b/packages/kbn-optimizer/src/worker/entry_point_creator.ts similarity index 52% rename from src/plugins/data/server/search/i_search_setup.ts rename to packages/kbn-optimizer/src/worker/entry_point_creator.ts index e4a4d50141201..f8f41b2e13422 100644 --- a/src/plugins/data/server/search/i_search_setup.ts +++ b/packages/kbn-optimizer/src/worker/entry_point_creator.ts @@ -17,24 +17,16 @@ * under the License. */ -import { IContextProvider } from 'kibana/server'; -import { ISearchContext } from './i_search_context'; -import { TRegisterSearchStrategyProvider, TSearchStrategyProvider } from './i_search_strategy'; +module.exports = function ({ + entries, +}: { + entries: Array<{ importId: string; requirePath: string }>; +}) { + const lines = entries.map(({ importId, requirePath }) => [ + `__kbnBundles__.define('${importId}', __webpack_require__, require.resolve('${requirePath}'))`, + ]); -/** - * The setup contract exposed by the Search plugin exposes the search strategy extension - * point. - */ -export interface ISearchSetup { - registerSearchStrategyContext: ( - pluginId: symbol, - strategyName: TContextName, - provider: IContextProvider, TContextName> - ) => void; - - /** - * Extension point exposed for other plugins to register their own search - * strategies. - */ - registerSearchStrategyProvider: TRegisterSearchStrategyProvider; -} + return { + code: lines.join('\n'), + }; +}; diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts index 4ab289d031d72..de5e9372e9e7a 100644 --- a/packages/kbn-optimizer/src/worker/run_compilers.ts +++ b/packages/kbn-optimizer/src/worker/run_compilers.ts @@ -35,7 +35,9 @@ import { WorkerConfig, ascending, parseFilePath, + BundleRefs, } from '../common'; +import { BundleRefModule } from './bundle_ref_module'; import { getWebpackConfig } from './webpack.config'; import { isFailureStats, failedStatsToErrorMessage } from './webpack_helpers'; import { @@ -43,7 +45,6 @@ import { isNormalModule, isIgnoredModule, isConcatenatedModule, - WebpackNormalModule, getModulePath, } from './webpack_helpers'; @@ -98,40 +99,43 @@ const observeCompiler = ( }); } - const normalModules = stats.compilation.modules.filter( - (module): module is WebpackNormalModule => { - if (isNormalModule(module)) { - return true; - } + const bundleRefExportIds: string[] = []; + const referencedFiles = new Set(); + let normalModuleCount = 0; + + for (const module of stats.compilation.modules) { + if (isNormalModule(module)) { + normalModuleCount += 1; + const path = getModulePath(module); + const parsedPath = parseFilePath(path); - if (isExternalModule(module) || isIgnoredModule(module) || isConcatenatedModule(module)) { - return false; + if (!parsedPath.dirs.includes('node_modules')) { + referencedFiles.add(path); + continue; } - throw new Error(`Unexpected module type: ${inspect(module)}`); + const nmIndex = parsedPath.dirs.lastIndexOf('node_modules'); + const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@'); + referencedFiles.add( + Path.join( + parsedPath.root, + ...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)), + 'package.json' + ) + ); + continue; } - ); - - const referencedFiles = new Set(); - for (const module of normalModules) { - const path = getModulePath(module); - const parsedPath = parseFilePath(path); + if (module instanceof BundleRefModule) { + bundleRefExportIds.push(module.exportId); + continue; + } - if (!parsedPath.dirs.includes('node_modules')) { - referencedFiles.add(path); + if (isExternalModule(module) || isIgnoredModule(module) || isConcatenatedModule(module)) { continue; } - const nmIndex = parsedPath.dirs.lastIndexOf('node_modules'); - const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@'); - referencedFiles.add( - Path.join( - parsedPath.root, - ...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)), - 'package.json' - ) - ); + throw new Error(`Unexpected module type: ${inspect(module)}`); } const files = Array.from(referencedFiles).sort(ascending((p) => p)); @@ -150,14 +154,15 @@ const observeCompiler = ( ); bundle.cache.set({ + bundleRefExportIds, optimizerCacheKey: workerConfig.optimizerCacheKey, cacheKey: bundle.createCacheKey(files, mtimes), - moduleCount: normalModules.length, + moduleCount: normalModuleCount, files, }); return compilerMsgs.compilerSuccess({ - moduleCount: normalModules.length, + moduleCount: normalModuleCount, }); }) ); @@ -185,8 +190,14 @@ const observeCompiler = ( /** * Run webpack compilers */ -export const runCompilers = (workerConfig: WorkerConfig, bundles: Bundle[]) => { - const multiCompiler = webpack(bundles.map((def) => getWebpackConfig(def, workerConfig))); +export const runCompilers = ( + workerConfig: WorkerConfig, + bundles: Bundle[], + bundleRefs: BundleRefs +) => { + const multiCompiler = webpack( + bundles.map((def) => getWebpackConfig(def, bundleRefs, workerConfig)) + ); return Rx.merge( /** diff --git a/packages/kbn-optimizer/src/worker/run_worker.ts b/packages/kbn-optimizer/src/worker/run_worker.ts index f83c69477f471..781cf83624a1e 100644 --- a/packages/kbn-optimizer/src/worker/run_worker.ts +++ b/packages/kbn-optimizer/src/worker/run_worker.ts @@ -17,9 +17,19 @@ * under the License. */ +import { inspect } from 'util'; + import * as Rx from 'rxjs'; +import { take, mergeMap } from 'rxjs/operators'; -import { parseBundles, parseWorkerConfig, WorkerMsg, isWorkerMsg, WorkerMsgs } from '../common'; +import { + parseBundles, + parseWorkerConfig, + WorkerMsg, + isWorkerMsg, + WorkerMsgs, + BundleRefs, +} from '../common'; import { runCompilers } from './run_compilers'; @@ -73,14 +83,38 @@ setInterval(() => { } }, 1000).unref(); +function assertInitMsg(msg: unknown): asserts msg is { args: string[] } { + if (typeof msg !== 'object' || !msg) { + throw new Error(`expected init message to be an object: ${inspect(msg)}`); + } + + const { args } = msg as Record; + if (!args || !Array.isArray(args) || !args.every((a) => typeof a === 'string')) { + throw new Error( + `expected init message to have an 'args' property that's an array of strings: ${inspect(msg)}` + ); + } +} + Rx.defer(() => { - const workerConfig = parseWorkerConfig(process.argv[2]); - const bundles = parseBundles(process.argv[3]); + process.send!('init'); + + return Rx.fromEvent<[unknown]>(process as any, 'message').pipe( + take(1), + mergeMap(([msg]) => { + assertInitMsg(msg); + process.send!('ready'); + + const workerConfig = parseWorkerConfig(msg.args[0]); + const bundles = parseBundles(msg.args[1]); + const bundleRefs = BundleRefs.parseSpec(msg.args[2]); - // set BROWSERSLIST_ENV so that style/babel loaders see it before running compilers - process.env.BROWSERSLIST_ENV = workerConfig.browserslistEnv; + // set BROWSERSLIST_ENV so that style/babel loaders see it before running compilers + process.env.BROWSERSLIST_ENV = workerConfig.browserslistEnv; - return runCompilers(workerConfig, bundles); + return runCompilers(workerConfig, bundles, bundleRefs); + }) + ); }).subscribe( (msg) => { send(msg); diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index d31c098ca1f2e..11f5544cd9274 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -17,10 +17,8 @@ * under the License. */ -import Fs from 'fs'; import Path from 'path'; -import normalizePath from 'normalize-path'; import { stringifyRequest } from 'loader-utils'; import webpack from 'webpack'; // @ts-ignore @@ -32,88 +30,22 @@ import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import CompressionPlugin from 'compression-webpack-plugin'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; -import { Bundle, WorkerConfig, parseDirPath, DisallowedSyntaxPlugin } from '../common'; +import { Bundle, BundleRefs, WorkerConfig, parseDirPath, DisallowedSyntaxPlugin } from '../common'; +import { BundleRefsPlugin } from './bundle_refs_plugin'; const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE; const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset'); const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); -const SHARED_BUNDLES = [ - { - type: 'entry', - id: 'core', - rootRelativeDir: 'src/core/public', - }, - { - type: 'plugin', - id: 'data', - rootRelativeDir: 'src/plugins/data/public', - }, - { - type: 'plugin', - id: 'kibanaReact', - rootRelativeDir: 'src/plugins/kibana_react/public', - }, - { - type: 'plugin', - id: 'kibanaUtils', - rootRelativeDir: 'src/plugins/kibana_utils/public', - }, - { - type: 'plugin', - id: 'esUiShared', - rootRelativeDir: 'src/plugins/es_ui_shared/public', - }, -]; - -/** - * Determine externals statements for require/import statements by looking - * for requests resolving to the primary public export of the data, kibanaReact, - * amd kibanaUtils plugins. If this module is being imported then rewrite - * the import to access the global `__kbnBundles__` variables and access - * the relavent properties from that global object. - * - * @param bundle - * @param context the directory containing the module which made `request` - * @param request the request for a module from a commonjs require() call or import statement - */ -function dynamicExternals(bundle: Bundle, context: string, request: string) { - // ignore imports that have loaders defined or are not relative seeming - if (request.includes('!') || !request.startsWith('.')) { - return; - } - - // determine the most acurate resolution string we can without running full resolution - const rootRelative = normalizePath( - Path.relative(bundle.sourceRoot, Path.resolve(context, request)) - ); - for (const sharedBundle of SHARED_BUNDLES) { - if ( - rootRelative !== sharedBundle.rootRelativeDir || - `${bundle.type}/${bundle.id}` === `${sharedBundle.type}/${sharedBundle.id}` - ) { - continue; - } - - return `__kbnBundles__['${sharedBundle.type}/${sharedBundle.id}']`; - } - - // import doesn't match a root public import - return undefined; -} - -export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { - const extensions = ['.js', '.ts', '.tsx', '.json']; - const entryExtension = extensions.find((ext) => - Fs.existsSync(Path.resolve(bundle.contextDir, bundle.entry) + ext) - ); +export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: WorkerConfig) { + const ENTRY_CREATOR = require.resolve('./entry_point_creator'); const commonConfig: webpack.Configuration = { node: { fs: 'empty' }, context: bundle.contextDir, cache: true, entry: { - [bundle.id]: `${bundle.entry}${entryExtension}`, + [bundle.id]: ENTRY_CREATOR, }, devtool: worker.dist ? false : '#cheap-source-map', @@ -128,27 +60,19 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { info.absoluteResourcePath )}${info.query}`, jsonpFunction: `${bundle.id}_bundle_jsonpfunction`, - // When the entry point is loaded, assign it's default export - // to a key on the global `__kbnBundles__` object. - library: ['__kbnBundles__', `${bundle.type}/${bundle.id}`], }, optimization: { noEmitOnErrors: true, }, - externals: [ - UiSharedDeps.externals, - function (context, request, cb) { - try { - cb(undefined, dynamicExternals(bundle, context, request)); - } catch (error) { - cb(error, undefined); - } - }, - ], + externals: [UiSharedDeps.externals], - plugins: [new CleanWebpackPlugin(), new DisallowedSyntaxPlugin()], + plugins: [ + new CleanWebpackPlugin(), + new DisallowedSyntaxPlugin(), + new BundleRefsPlugin(bundle, bundleRefs), + ], module: { // no parse rules for a few known large packages which have no require() statements @@ -157,16 +81,39 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { noParse: [ /[\/\\]node_modules[\/\\]elasticsearch-browser[\/\\]/, /[\/\\]node_modules[\/\\]lodash[\/\\]index\.js$/, - /[\/\\]node_modules[\/\\]vega-lib[\/\\]build[\/\\]vega\.js$/, + /[\/\\]node_modules[\/\\]vega[\/\\]build[\/\\]vega\.js$/, ], rules: [ { - include: [`${Path.resolve(bundle.contextDir, bundle.entry)}${entryExtension}`], - loader: UiSharedDeps.publicPathLoader, - options: { - key: bundle.id, - }, + include: [ENTRY_CREATOR], + use: [ + { + loader: UiSharedDeps.publicPathLoader, + options: { + key: bundle.id, + }, + }, + { + loader: require.resolve('val-loader'), + options: { + entries: bundle.publicDirNames.map((name) => { + const absolute = Path.resolve(bundle.contextDir, name); + const newContext = Path.dirname(ENTRY_CREATOR); + const importId = `${bundle.type}/${bundle.id}/${name}`; + + // relative path from context of the ENTRY_CREATOR, with linux path separators + let requirePath = Path.relative(newContext, absolute).split('\\').join('/'); + if (!requirePath.startsWith('.')) { + // ensure requirePath is identified by node as relative + requirePath = `./${requirePath}`; + } + + return { importId, requirePath }; + }), + }, + }, + ], }, { test: /\.css$/, @@ -310,7 +257,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { }, resolve: { - extensions, + extensions: ['.js', '.ts', '.tsx', 'json'], mainFields: ['browser', 'main'], alias: { tinymath: require.resolve('tinymath/lib/tinymath.es5.js'), diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 13bddfd0f110a..f6d008c9bf9be 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__(497); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(498); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); @@ -105,10 +105,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(162); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return _utils_project__WEBPACK_IMPORTED_MODULE_3__["Project"]; }); -/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(270); +/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(271); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__["copyWorkspacePackages"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(271); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(272); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -152,7 +152,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _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__(126); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(489); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(490); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(142); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -1576,10 +1576,12 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__metadata", function() { return __metadata; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__awaiter", function() { return __awaiter; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__generator", function() { return __generator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__createBinding", function() { return __createBinding; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__exportStar", function() { return __exportStar; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__values", function() { return __values; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spreadArrays", function() { return __spreadArrays; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); @@ -1587,19 +1589,21 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__makeTemplateObject", function() { return __makeTemplateObject; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importStar", function() { return __importStar; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldGet", function() { return __classPrivateFieldGet; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldSet", function() { return __classPrivateFieldSet; }); /*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 +Copyright (c) Microsoft Corporation. -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ @@ -1632,8 +1636,10 @@ function __rest(s, e) { for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } return t; } @@ -1653,10 +1659,11 @@ function __metadata(metadataKey, metadataValue) { } function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } @@ -1689,19 +1696,25 @@ function __generator(thisArg, body) { } } +function __createBinding(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +} + function __exportStar(m, exports) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) exports[p] = m[p]; } function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); - return { + if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __read(o, n) { @@ -1727,6 +1740,14 @@ function __spread() { return ar; } +function __spreadArrays() { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; + function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } @@ -1773,6 +1794,21 @@ function __importStar(mod) { function __importDefault(mod) { return (mod && mod.__esModule) ? mod : { default: mod }; } + +function __classPrivateFieldGet(receiver, privateMap) { + if (!privateMap.has(receiver)) { + throw new TypeError("attempted to get private field on non-instance"); + } + return privateMap.get(receiver); +} + +function __classPrivateFieldSet(receiver, privateMap, value) { + if (!privateMap.has(receiver)) { + throw new TypeError("attempted to set private field on non-instance"); + } + privateMap.set(receiver, value); + return value; +} /***/ }), @@ -8693,9 +8729,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__(127); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(279); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(388); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(389); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(280); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(389); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(390); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8736,8 +8772,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(142); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(143); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144); -/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(272); -/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(278); +/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(273); +/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(279); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8810,13 +8846,13 @@ const BootstrapCommand = { if (valid) { _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].debug(`[${project.name}] cache up to date`); + cachedProjectCount += 1; } caches.set(project, { file, valid }); - cachedProjectCount += 1; } } @@ -10745,7 +10781,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(161); /* harmony import */ var _project__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(162); -/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(270); +/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(271); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -14356,7 +14392,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(161); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(142); /* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(163); -/* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(225); +/* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(226); 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; } @@ -14603,7 +14639,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isLinkDependency", function() { return isLinkDependency; }); /* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(164); /* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(read_pkg__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); +/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); /* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(write_pkg__WEBPACK_IMPORTED_MODULE_1__); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -16200,7 +16236,7 @@ module.exports = normalize var fixer = __webpack_require__(181) normalize.fixer = fixer -var makeWarning = __webpack_require__(203) +var makeWarning = __webpack_require__(204) var fieldsToFix = ['name','version','description','repository','modules','scripts' ,'files','bin','man','bugs','keywords','readme','homepage','license'] @@ -16245,9 +16281,9 @@ var validateLicense = __webpack_require__(183); var hostedGitInfo = __webpack_require__(188) var isBuiltinModule = __webpack_require__(192).isCore var depTypes = ["dependencies","devDependencies","optionalDependencies"] -var extractDescription = __webpack_require__(201) +var extractDescription = __webpack_require__(202) var url = __webpack_require__(189) -var typos = __webpack_require__(202) +var typos = __webpack_require__(203) var fixer = module.exports = { // default warning function @@ -20197,13 +20233,11 @@ GitHost.prototype.toString = function (opts) { /* 192 */ /***/ (function(module, exports, __webpack_require__) { -var core = __webpack_require__(193); -var async = __webpack_require__(195); -async.core = core; -async.isCore = function isCore(x) { return core[x]; }; -async.sync = __webpack_require__(200); +var async = __webpack_require__(193); +async.core = __webpack_require__(199); +async.isCore = __webpack_require__(198); +async.sync = __webpack_require__(201); -exports = async; module.exports = async; @@ -20211,77 +20245,14 @@ module.exports = async; /* 193 */ /***/ (function(module, exports, __webpack_require__) { -var current = (process.versions && process.versions.node && process.versions.node.split('.')) || []; - -function specifierIncluded(specifier) { - var parts = specifier.split(' '); - var op = parts.length > 1 ? parts[0] : '='; - var versionParts = (parts.length > 1 ? parts[1] : parts[0]).split('.'); - - for (var i = 0; i < 3; ++i) { - var cur = Number(current[i] || 0); - var ver = Number(versionParts[i] || 0); - if (cur === ver) { - continue; // eslint-disable-line no-restricted-syntax, no-continue - } - if (op === '<') { - return cur < ver; - } else if (op === '>=') { - return cur >= ver; - } else { - return false; - } - } - return op === '>='; -} - -function matchesRange(range) { - var specifiers = range.split(/ ?&& ?/); - if (specifiers.length === 0) { return false; } - for (var i = 0; i < specifiers.length; ++i) { - if (!specifierIncluded(specifiers[i])) { return false; } - } - return true; -} - -function versionIncluded(specifierValue) { - if (typeof specifierValue === 'boolean') { return specifierValue; } - if (specifierValue && typeof specifierValue === 'object') { - for (var i = 0; i < specifierValue.length; ++i) { - if (matchesRange(specifierValue[i])) { return true; } - } - return false; - } - return matchesRange(specifierValue); -} - -var data = __webpack_require__(194); - -var core = {}; -for (var mod in data) { // eslint-disable-line no-restricted-syntax - if (Object.prototype.hasOwnProperty.call(data, mod)) { - core[mod] = versionIncluded(data[mod]); - } -} -module.exports = core; - - -/***/ }), -/* 194 */ -/***/ (function(module) { - -module.exports = JSON.parse("{\"assert\":true,\"async_hooks\":\">= 8\",\"buffer_ieee754\":\"< 0.9.7\",\"buffer\":true,\"child_process\":true,\"cluster\":true,\"console\":true,\"constants\":true,\"crypto\":true,\"_debugger\":\"< 8\",\"dgram\":true,\"dns\":true,\"domain\":true,\"events\":true,\"freelist\":\"< 6\",\"fs\":true,\"fs/promises\":\">= 10 && < 10.1\",\"_http_agent\":\">= 0.11.1\",\"_http_client\":\">= 0.11.1\",\"_http_common\":\">= 0.11.1\",\"_http_incoming\":\">= 0.11.1\",\"_http_outgoing\":\">= 0.11.1\",\"_http_server\":\">= 0.11.1\",\"http\":true,\"http2\":\">= 8.8\",\"https\":true,\"inspector\":\">= 8.0.0\",\"_linklist\":\"< 8\",\"module\":true,\"net\":true,\"node-inspect/lib/_inspect\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6.0\",\"os\":true,\"path\":true,\"perf_hooks\":\">= 8.5\",\"process\":\">= 1\",\"punycode\":true,\"querystring\":true,\"readline\":true,\"repl\":true,\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"_stream_transform\":\">= 0.9.4\",\"_stream_wrap\":\">= 1.4.1\",\"_stream_passthrough\":\">= 0.9.4\",\"_stream_readable\":\">= 0.9.4\",\"_stream_writable\":\">= 0.9.4\",\"stream\":true,\"string_decoder\":true,\"sys\":true,\"timers\":true,\"_tls_common\":\">= 0.11.13\",\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"tls\":true,\"trace_events\":\">= 10\",\"tty\":true,\"url\":true,\"util\":true,\"v8/tools/arguments\":\">= 10\",\"v8/tools/codemap\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/consarray\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/csvparser\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/logreader\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/profile_view\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/splaytree\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8\":\">= 1\",\"vm\":true,\"worker_threads\":\">= 11.7\",\"zlib\":true}"); - -/***/ }), -/* 195 */ -/***/ (function(module, exports, __webpack_require__) { - -var core = __webpack_require__(193); var fs = __webpack_require__(132); var path = __webpack_require__(4); -var caller = __webpack_require__(196); -var nodeModulesPaths = __webpack_require__(197); -var normalizeOptions = __webpack_require__(199); +var caller = __webpack_require__(194); +var nodeModulesPaths = __webpack_require__(195); +var normalizeOptions = __webpack_require__(197); +var isCore = __webpack_require__(198); + +var realpathFS = fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; var defaultIsFile = function isFile(file, cb) { fs.stat(file, function (err, stat) { @@ -20293,6 +20264,39 @@ var defaultIsFile = function isFile(file, cb) { }); }; +var defaultIsDir = function isDirectory(dir, cb) { + fs.stat(dir, function (err, stat) { + if (!err) { + return cb(null, stat.isDirectory()); + } + if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); + return cb(err); + }); +}; + +var defaultRealpath = function realpath(x, cb) { + realpathFS(x, function (realpathErr, realPath) { + if (realpathErr && realpathErr.code !== 'ENOENT') cb(realpathErr); + else cb(null, realpathErr ? x : realPath); + }); +}; + +var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) { + if (opts && opts.preserveSymlinks === false) { + realpath(x, cb); + } else { + cb(null, x); + } +}; + +var getPackageCandidates = function getPackageCandidates(x, start, opts) { + var dirs = nodeModulesPaths(start, opts, x); + for (var i = 0; i < dirs.length; i++) { + dirs[i] = path.join(dirs[i], x); + } + return dirs; +}; + module.exports = function resolve(x, options, callback) { var cb = callback; var opts = options; @@ -20310,7 +20314,10 @@ module.exports = function resolve(x, options, callback) { opts = normalizeOptions(x, opts); var isFile = opts.isFile || defaultIsFile; + var isDirectory = opts.isDirectory || defaultIsDir; var readFile = opts.readFile || fs.readFile; + var realpath = opts.realpath || defaultRealpath; + var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; var basedir = opts.basedir || path.dirname(caller()); @@ -20321,28 +20328,37 @@ module.exports = function resolve(x, options, callback) { // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory var absoluteStart = path.resolve(basedir); - if (opts.preserveSymlinks === false) { - fs.realpath(absoluteStart, function (realPathErr, realStart) { - if (realPathErr && realPathErr.code !== 'ENOENT') cb(err); - else init(realPathErr ? absoluteStart : realStart); - }); - } else { - init(absoluteStart); - } + maybeRealpath( + realpath, + absoluteStart, + opts, + function (err, realStart) { + if (err) cb(err); + else init(realStart); + } + ); var res; function init(basedir) { if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { res = path.resolve(basedir, x); - if (x === '..' || x.slice(-1) === '/') res += '/'; + if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; if ((/\/$/).test(x) && res === basedir) { loadAsDirectory(res, opts.package, onfile); } else loadAsFile(res, opts.package, onfile); + } else if (isCore(x)) { + return cb(null, x); } else loadNodeModules(x, basedir, function (err, n, pkg) { if (err) cb(err); - else if (n) cb(null, n, pkg); - else if (core[x]) return cb(null, x); - else { + else if (n) { + return maybeRealpath(realpath, n, opts, function (err, realN) { + if (err) { + cb(err); + } else { + cb(null, realN, pkg); + } + }); + } else { var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); moduleError.code = 'MODULE_NOT_FOUND'; cb(moduleError); @@ -20355,8 +20371,15 @@ module.exports = function resolve(x, options, callback) { else if (m) cb(null, m, pkg); else loadAsDirectory(res, function (err, d, pkg) { if (err) cb(err); - else if (d) cb(null, d, pkg); - else { + else if (d) { + maybeRealpath(realpath, d, opts, function (err, realD) { + if (err) { + cb(err); + } else { + cb(null, realD, pkg); + } + }); + } else { var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); moduleError.code = 'MODULE_NOT_FOUND'; cb(moduleError); @@ -20413,19 +20436,22 @@ module.exports = function resolve(x, options, callback) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); - var pkgfile = path.join(dir, 'package.json'); - isFile(pkgfile, function (err, ex) { - // on err, ex is false - if (!ex) return loadpkg(path.dirname(dir), cb); + maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) { + if (unwrapErr) return loadpkg(path.dirname(dir), cb); + var pkgfile = path.join(pkgdir, 'package.json'); + isFile(pkgfile, function (err, ex) { + // on err, ex is false + if (!ex) return loadpkg(path.dirname(dir), cb); - readFile(pkgfile, function (err, body) { - if (err) cb(err); - try { var pkg = JSON.parse(body); } catch (jsonErr) {} + readFile(pkgfile, function (err, body) { + if (err) cb(err); + try { var pkg = JSON.parse(body); } catch (jsonErr) {} - if (pkg && opts.packageFilter) { - pkg = opts.packageFilter(pkg, pkgfile); - } - cb(null, pkg, dir); + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); + } + cb(null, pkg, dir); + }); }); }); } @@ -20438,46 +20464,49 @@ module.exports = function resolve(x, options, callback) { fpkg = opts.package; } - var pkgfile = path.join(x, 'package.json'); - isFile(pkgfile, function (err, ex) { - if (err) return cb(err); - if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); - - readFile(pkgfile, function (err, body) { + maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) { + if (unwrapErr) return cb(unwrapErr); + var pkgfile = path.join(pkgdir, 'package.json'); + isFile(pkgfile, function (err, ex) { if (err) return cb(err); - try { - var pkg = JSON.parse(body); - } catch (jsonErr) {} + if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); - if (opts.packageFilter) { - pkg = opts.packageFilter(pkg, pkgfile); - } + readFile(pkgfile, function (err, body) { + if (err) return cb(err); + try { + var pkg = JSON.parse(body); + } catch (jsonErr) {} - if (pkg.main) { - if (typeof pkg.main !== 'string') { - var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); - mainError.code = 'INVALID_PACKAGE_MAIN'; - return cb(mainError); - } - if (pkg.main === '.' || pkg.main === './') { - pkg.main = 'index'; + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); } - loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { - if (err) return cb(err); - if (m) return cb(null, m, pkg); - if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); - var dir = path.resolve(x, pkg.main); - loadAsDirectory(dir, pkg, function (err, n, pkg) { + if (pkg && pkg.main) { + if (typeof pkg.main !== 'string') { + var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); + mainError.code = 'INVALID_PACKAGE_MAIN'; + return cb(mainError); + } + if (pkg.main === '.' || pkg.main === './') { + pkg.main = 'index'; + } + loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { if (err) return cb(err); - if (n) return cb(null, n, pkg); - loadAsFile(path.join(x, 'index'), pkg, cb); + if (m) return cb(null, m, pkg); + if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); + + var dir = path.resolve(x, pkg.main); + loadAsDirectory(dir, pkg, function (err, n, pkg) { + if (err) return cb(err); + if (n) return cb(null, n, pkg); + loadAsFile(path.join(x, 'index'), pkg, cb); + }); }); - }); - return; - } + return; + } - loadAsFile(path.join(x, '/index'), pkg, cb); + loadAsFile(path.join(x, '/index'), pkg, cb); + }); }); }); } @@ -20486,13 +20515,18 @@ module.exports = function resolve(x, options, callback) { if (dirs.length === 0) return cb(null, undefined); var dir = dirs[0]; - var file = path.join(dir, x); - loadAsFile(file, opts.package, onfile); + isDirectory(path.dirname(dir), isdir); + + function isdir(err, isdir) { + if (err) return cb(err); + if (!isdir) return processDirs(cb, dirs.slice(1)); + loadAsFile(dir, opts.package, onfile); + } function onfile(err, m, pkg) { if (err) return cb(err); if (m) return cb(null, m, pkg); - loadAsDirectory(path.join(dir, x), opts.package, ondir); + loadAsDirectory(dir, opts.package, ondir); } function ondir(err, n, pkg) { @@ -20502,13 +20536,17 @@ module.exports = function resolve(x, options, callback) { } } function loadNodeModules(x, start, cb) { - processDirs(cb, nodeModulesPaths(start, opts, x)); + var thunk = function () { return getPackageCandidates(x, start, opts); }; + processDirs( + cb, + packageIterator ? packageIterator(x, start, thunk, opts) : thunk() + ); } }; /***/ }), -/* 196 */ +/* 194 */ /***/ (function(module, exports) { module.exports = function () { @@ -20522,11 +20560,11 @@ module.exports = function () { /***/ }), -/* 197 */ +/* 195 */ /***/ (function(module, exports, __webpack_require__) { var path = __webpack_require__(4); -var parse = path.parse || __webpack_require__(198); +var parse = path.parse || __webpack_require__(196); var getNodeModulesDirs = function getNodeModulesDirs(absoluteStart, modules) { var prefix = '/'; @@ -20545,7 +20583,7 @@ var getNodeModulesDirs = function getNodeModulesDirs(absoluteStart, modules) { return paths.reduce(function (dirs, aPath) { return dirs.concat(modules.map(function (moduleDir) { - return path.join(prefix, aPath, moduleDir); + return path.resolve(prefix, aPath, moduleDir); })); }, []); }; @@ -20570,7 +20608,7 @@ module.exports = function nodeModulesPaths(start, opts, request) { /***/ }), -/* 198 */ +/* 196 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -20670,7 +20708,7 @@ module.exports.win32 = win32.parse; /***/ }), -/* 199 */ +/* 197 */ /***/ (function(module, exports) { module.exports = function (x, opts) { @@ -20685,16 +20723,94 @@ module.exports = function (x, opts) { }; +/***/ }), +/* 198 */ +/***/ (function(module, exports, __webpack_require__) { + +var core = __webpack_require__(199); + +module.exports = function isCore(x) { + return Object.prototype.hasOwnProperty.call(core, x); +}; + + +/***/ }), +/* 199 */ +/***/ (function(module, exports, __webpack_require__) { + +var current = (process.versions && process.versions.node && process.versions.node.split('.')) || []; + +function specifierIncluded(specifier) { + var parts = specifier.split(' '); + var op = parts.length > 1 ? parts[0] : '='; + var versionParts = (parts.length > 1 ? parts[1] : parts[0]).split('.'); + + for (var i = 0; i < 3; ++i) { + var cur = Number(current[i] || 0); + var ver = Number(versionParts[i] || 0); + if (cur === ver) { + continue; // eslint-disable-line no-restricted-syntax, no-continue + } + if (op === '<') { + return cur < ver; + } else if (op === '>=') { + return cur >= ver; + } else { + return false; + } + } + return op === '>='; +} + +function matchesRange(range) { + var specifiers = range.split(/ ?&& ?/); + if (specifiers.length === 0) { return false; } + for (var i = 0; i < specifiers.length; ++i) { + if (!specifierIncluded(specifiers[i])) { return false; } + } + return true; +} + +function versionIncluded(specifierValue) { + if (typeof specifierValue === 'boolean') { return specifierValue; } + if (specifierValue && typeof specifierValue === 'object') { + for (var i = 0; i < specifierValue.length; ++i) { + if (matchesRange(specifierValue[i])) { return true; } + } + return false; + } + return matchesRange(specifierValue); +} + +var data = __webpack_require__(200); + +var core = {}; +for (var mod in data) { // eslint-disable-line no-restricted-syntax + if (Object.prototype.hasOwnProperty.call(data, mod)) { + core[mod] = versionIncluded(data[mod]); + } +} +module.exports = core; + + /***/ }), /* 200 */ +/***/ (function(module) { + +module.exports = JSON.parse("{\"assert\":true,\"async_hooks\":\">= 8\",\"buffer_ieee754\":\"< 0.9.7\",\"buffer\":true,\"child_process\":true,\"cluster\":true,\"console\":true,\"constants\":true,\"crypto\":true,\"_debug_agent\":\">= 1 && < 8\",\"_debugger\":\"< 8\",\"dgram\":true,\"dns\":true,\"domain\":true,\"events\":true,\"freelist\":\"< 6\",\"fs\":true,\"fs/promises\":[\">= 10 && < 10.1\",\">= 14\"],\"_http_agent\":\">= 0.11.1\",\"_http_client\":\">= 0.11.1\",\"_http_common\":\">= 0.11.1\",\"_http_incoming\":\">= 0.11.1\",\"_http_outgoing\":\">= 0.11.1\",\"_http_server\":\">= 0.11.1\",\"http\":true,\"http2\":\">= 8.8\",\"https\":true,\"inspector\":\">= 8.0.0\",\"_linklist\":\"< 8\",\"module\":true,\"net\":true,\"node-inspect/lib/_inspect\":\">= 7.6.0 && < 12\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6.0 && < 12\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6.0 && < 12\",\"os\":true,\"path\":true,\"perf_hooks\":\">= 8.5\",\"process\":\">= 1\",\"punycode\":true,\"querystring\":true,\"readline\":true,\"repl\":true,\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"_stream_transform\":\">= 0.9.4\",\"_stream_wrap\":\">= 1.4.1\",\"_stream_passthrough\":\">= 0.9.4\",\"_stream_readable\":\">= 0.9.4\",\"_stream_writable\":\">= 0.9.4\",\"stream\":true,\"string_decoder\":true,\"sys\":true,\"timers\":true,\"_tls_common\":\">= 0.11.13\",\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"tls\":true,\"trace_events\":\">= 10\",\"tty\":true,\"url\":true,\"util\":true,\"v8/tools/arguments\":\">= 10 && < 12\",\"v8/tools/codemap\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8/tools/consarray\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8/tools/csvparser\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8/tools/logreader\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8/tools/profile_view\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8/tools/splaytree\":[\">= 4.4.0 && < 5\",\">= 5.2.0 && < 12\"],\"v8\":\">= 1\",\"vm\":true,\"wasi\":\">= 13.4 && < 13.5\",\"worker_threads\":\">= 11.7\",\"zlib\":true}"); + +/***/ }), +/* 201 */ /***/ (function(module, exports, __webpack_require__) { -var core = __webpack_require__(193); +var isCore = __webpack_require__(198); var fs = __webpack_require__(132); var path = __webpack_require__(4); -var caller = __webpack_require__(196); -var nodeModulesPaths = __webpack_require__(197); -var normalizeOptions = __webpack_require__(199); +var caller = __webpack_require__(194); +var nodeModulesPaths = __webpack_require__(195); +var normalizeOptions = __webpack_require__(197); + +var realpathFS = fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; var defaultIsFile = function isFile(file) { try { @@ -20706,7 +20822,43 @@ var defaultIsFile = function isFile(file) { return stat.isFile() || stat.isFIFO(); }; -module.exports = function (x, options) { +var defaultIsDir = function isDirectory(dir) { + try { + var stat = fs.statSync(dir); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; + throw e; + } + return stat.isDirectory(); +}; + +var defaultRealpathSync = function realpathSync(x) { + try { + return realpathFS(x); + } catch (realpathErr) { + if (realpathErr.code !== 'ENOENT') { + throw realpathErr; + } + } + return x; +}; + +var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) { + if (opts && opts.preserveSymlinks === false) { + return realpathSync(x); + } + return x; +}; + +var getPackageCandidates = function getPackageCandidates(x, start, opts) { + var dirs = nodeModulesPaths(start, opts, x); + for (var i = 0; i < dirs.length; i++) { + dirs[i] = path.join(dirs[i], x); + } + return dirs; +}; + +module.exports = function resolveSync(x, options) { if (typeof x !== 'string') { throw new TypeError('Path must be a string.'); } @@ -20714,6 +20866,9 @@ module.exports = function (x, options) { var isFile = opts.isFile || defaultIsFile; var readFileSync = opts.readFileSync || fs.readFileSync; + var isDirectory = opts.isDirectory || defaultIsDir; + var realpathSync = opts.realpathSync || defaultRealpathSync; + var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; var basedir = opts.basedir || path.dirname(caller()); @@ -20722,30 +20877,20 @@ module.exports = function (x, options) { opts.paths = opts.paths || []; // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory - var absoluteStart = path.resolve(basedir); - - if (opts.preserveSymlinks === false) { - try { - absoluteStart = fs.realpathSync(absoluteStart); - } catch (realPathErr) { - if (realPathErr.code !== 'ENOENT') { - throw realPathErr; - } - } - } + var absoluteStart = maybeRealpathSync(realpathSync, path.resolve(basedir), opts); if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { var res = path.resolve(absoluteStart, x); - if (x === '..' || x.slice(-1) === '/') res += '/'; + if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; var m = loadAsFileSync(res) || loadAsDirectorySync(res); - if (m) return m; + if (m) return maybeRealpathSync(realpathSync, m, opts); + } else if (isCore(x)) { + return x; } else { var n = loadNodeModulesSync(x, absoluteStart); - if (n) return n; + if (n) return maybeRealpathSync(realpathSync, n, opts); } - if (core[x]) return x; - var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); err.code = 'MODULE_NOT_FOUND'; throw err; @@ -20780,7 +20925,7 @@ module.exports = function (x, options) { } if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; - var pkgfile = path.join(dir, 'package.json'); + var pkgfile = path.join(maybeRealpathSync(realpathSync, dir, opts), 'package.json'); if (!isFile(pkgfile)) { return loadpkg(path.dirname(dir)); @@ -20793,25 +20938,27 @@ module.exports = function (x, options) { } catch (jsonErr) {} if (pkg && opts.packageFilter) { - pkg = opts.packageFilter(pkg, dir); + // v2 will pass pkgfile + pkg = opts.packageFilter(pkg, /*pkgfile,*/ dir); // eslint-disable-line spaced-comment } return { pkg: pkg, dir: dir }; } function loadAsDirectorySync(x) { - var pkgfile = path.join(x, '/package.json'); + var pkgfile = path.join(maybeRealpathSync(realpathSync, x, opts), '/package.json'); if (isFile(pkgfile)) { try { var body = readFileSync(pkgfile, 'UTF8'); var pkg = JSON.parse(body); } catch (e) {} - if (opts.packageFilter) { - pkg = opts.packageFilter(pkg, x); + if (pkg && opts.packageFilter) { + // v2 will pass pkgfile + pkg = opts.packageFilter(pkg, /*pkgfile,*/ x); // eslint-disable-line spaced-comment } - if (pkg.main) { + if (pkg && pkg.main) { if (typeof pkg.main !== 'string') { var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); mainError.code = 'INVALID_PACKAGE_MAIN'; @@ -20833,20 +20980,24 @@ module.exports = function (x, options) { } function loadNodeModulesSync(x, start) { - var dirs = nodeModulesPaths(start, opts, x); + var thunk = function () { return getPackageCandidates(x, start, opts); }; + var dirs = packageIterator ? packageIterator(x, start, thunk, opts) : thunk(); + for (var i = 0; i < dirs.length; i++) { var dir = dirs[i]; - var m = loadAsFileSync(path.join(dir, '/', x)); - if (m) return m; - var n = loadAsDirectorySync(path.join(dir, '/', x)); - if (n) return n; + if (isDirectory(path.dirname(dir))) { + var m = loadAsFileSync(dir); + if (m) return m; + var n = loadAsDirectorySync(dir); + if (n) return n; + } } } }; /***/ }), -/* 201 */ +/* 202 */ /***/ (function(module, exports) { module.exports = extractDescription @@ -20866,17 +21017,17 @@ function extractDescription (d) { /***/ }), -/* 202 */ +/* 203 */ /***/ (function(module) { module.exports = JSON.parse("{\"topLevel\":{\"dependancies\":\"dependencies\",\"dependecies\":\"dependencies\",\"depdenencies\":\"dependencies\",\"devEependencies\":\"devDependencies\",\"depends\":\"dependencies\",\"dev-dependencies\":\"devDependencies\",\"devDependences\":\"devDependencies\",\"devDepenencies\":\"devDependencies\",\"devdependencies\":\"devDependencies\",\"repostitory\":\"repository\",\"repo\":\"repository\",\"prefereGlobal\":\"preferGlobal\",\"hompage\":\"homepage\",\"hampage\":\"homepage\",\"autohr\":\"author\",\"autor\":\"author\",\"contributers\":\"contributors\",\"publicationConfig\":\"publishConfig\",\"script\":\"scripts\"},\"bugs\":{\"web\":\"url\",\"name\":\"url\"},\"script\":{\"server\":\"start\",\"tests\":\"test\"}}"); /***/ }), -/* 203 */ +/* 204 */ /***/ (function(module, exports, __webpack_require__) { var util = __webpack_require__(111) -var messages = __webpack_require__(204) +var messages = __webpack_require__(205) module.exports = function() { var args = Array.prototype.slice.call(arguments, 0) @@ -20901,20 +21052,20 @@ function makeTypoWarning (providedName, probableName, field) { /***/ }), -/* 204 */ +/* 205 */ /***/ (function(module) { module.exports = JSON.parse("{\"repositories\":\"'repositories' (plural) Not supported. Please pick one as the 'repository' field\",\"missingRepository\":\"No repository field.\",\"brokenGitUrl\":\"Probably broken git url: %s\",\"nonObjectScripts\":\"scripts must be an object\",\"nonStringScript\":\"script values must be string commands\",\"nonArrayFiles\":\"Invalid 'files' member\",\"invalidFilename\":\"Invalid filename in 'files' list: %s\",\"nonArrayBundleDependencies\":\"Invalid 'bundleDependencies' list. Must be array of package names\",\"nonStringBundleDependency\":\"Invalid bundleDependencies member: %s\",\"nonDependencyBundleDependency\":\"Non-dependency in bundleDependencies: %s\",\"nonObjectDependencies\":\"%s field must be an object\",\"nonStringDependency\":\"Invalid dependency: %s %s\",\"deprecatedArrayDependencies\":\"specifying %s as array is deprecated\",\"deprecatedModules\":\"modules field is deprecated\",\"nonArrayKeywords\":\"keywords should be an array of strings\",\"nonStringKeyword\":\"keywords should be an array of strings\",\"conflictingName\":\"%s is also the name of a node core module.\",\"nonStringDescription\":\"'description' field should be a string\",\"missingDescription\":\"No description\",\"missingReadme\":\"No README data\",\"missingLicense\":\"No license field.\",\"nonEmailUrlBugsString\":\"Bug string field must be url, email, or {email,url}\",\"nonUrlBugsUrlField\":\"bugs.url field must be a string url. Deleted.\",\"nonEmailBugsEmailField\":\"bugs.email field must be a string email. Deleted.\",\"emptyNormalizedBugs\":\"Normalized value of bugs field is an empty object. Deleted.\",\"nonUrlHomepage\":\"homepage field must be a string url. Deleted.\",\"invalidLicense\":\"license should be a valid SPDX license expression\",\"typo\":\"%s should probably be %s.\"}"); /***/ }), -/* 205 */ +/* 206 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const writeJsonFile = __webpack_require__(206); -const sortKeys = __webpack_require__(220); +const writeJsonFile = __webpack_require__(207); +const sortKeys = __webpack_require__(221); const dependencyKeys = new Set([ 'dependencies', @@ -20979,18 +21130,18 @@ module.exports.sync = (filePath, data, options) => { /***/ }), -/* 206 */ +/* 207 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const fs = __webpack_require__(207); -const writeFileAtomic = __webpack_require__(211); -const sortKeys = __webpack_require__(220); -const makeDir = __webpack_require__(222); -const pify = __webpack_require__(223); -const detectIndent = __webpack_require__(224); +const fs = __webpack_require__(208); +const writeFileAtomic = __webpack_require__(212); +const sortKeys = __webpack_require__(221); +const makeDir = __webpack_require__(223); +const pify = __webpack_require__(224); +const detectIndent = __webpack_require__(225); const init = (fn, filePath, data, options) => { if (!filePath) { @@ -21062,13 +21213,13 @@ module.exports.sync = (filePath, data, options) => { /***/ }), -/* 207 */ +/* 208 */ /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(132) -var polyfills = __webpack_require__(208) -var legacy = __webpack_require__(209) -var clone = __webpack_require__(210) +var polyfills = __webpack_require__(209) +var legacy = __webpack_require__(210) +var clone = __webpack_require__(211) var queue = [] @@ -21347,7 +21498,7 @@ function retry () { /***/ }), -/* 208 */ +/* 209 */ /***/ (function(module, exports, __webpack_require__) { var constants = __webpack_require__(134) @@ -21682,7 +21833,7 @@ function patch (fs) { /***/ }), -/* 209 */ +/* 210 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(136).Stream @@ -21806,7 +21957,7 @@ function legacy (fs) { /***/ }), -/* 210 */ +/* 211 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -21832,7 +21983,7 @@ function clone (obj) { /***/ }), -/* 211 */ +/* 212 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -21842,9 +21993,9 @@ module.exports.sync = writeFileSync module.exports._getTmpname = getTmpname // for testing module.exports._cleanupOnExit = cleanupOnExit -var fs = __webpack_require__(212) -var MurmurHash3 = __webpack_require__(216) -var onExit = __webpack_require__(217) +var fs = __webpack_require__(213) +var MurmurHash3 = __webpack_require__(217) +var onExit = __webpack_require__(218) var path = __webpack_require__(4) var activeFiles = {} @@ -21852,7 +22003,7 @@ var activeFiles = {} /* istanbul ignore next */ var threadId = (function getId () { try { - var workerThreads = __webpack_require__(219) + var workerThreads = __webpack_require__(220) /// if we are in main thread, this is set to `0` return workerThreads.threadId @@ -22077,12 +22228,12 @@ function writeFileSync (filename, data, options) { /***/ }), -/* 212 */ +/* 213 */ /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(132) -var polyfills = __webpack_require__(213) -var legacy = __webpack_require__(215) +var polyfills = __webpack_require__(214) +var legacy = __webpack_require__(216) var queue = [] var util = __webpack_require__(111) @@ -22106,7 +22257,7 @@ if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { }) } -module.exports = patch(__webpack_require__(214)) +module.exports = patch(__webpack_require__(215)) if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { module.exports = patch(fs) } @@ -22345,10 +22496,10 @@ function retry () { /***/ }), -/* 213 */ +/* 214 */ /***/ (function(module, exports, __webpack_require__) { -var fs = __webpack_require__(214) +var fs = __webpack_require__(215) var constants = __webpack_require__(134) var origCwd = process.cwd @@ -22681,7 +22832,7 @@ function chownErOk (er) { /***/ }), -/* 214 */ +/* 215 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22709,7 +22860,7 @@ function clone (obj) { /***/ }), -/* 215 */ +/* 216 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(136).Stream @@ -22833,7 +22984,7 @@ function legacy (fs) { /***/ }), -/* 216 */ +/* 217 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -22975,14 +23126,14 @@ function legacy (fs) { /***/ }), -/* 217 */ +/* 218 */ /***/ (function(module, exports, __webpack_require__) { // Note: since nyc uses this module to output coverage, any lines // that are in the direct sync flow of nyc's outputCoverage are // ignored, since we can never get coverage for them. var assert = __webpack_require__(138) -var signals = __webpack_require__(218) +var signals = __webpack_require__(219) var EE = __webpack_require__(154) /* istanbul ignore if */ @@ -23138,7 +23289,7 @@ function processEmit (ev, arg) { /***/ }), -/* 218 */ +/* 219 */ /***/ (function(module, exports) { // This is not the set of all possible signals. @@ -23197,18 +23348,18 @@ if (process.platform === 'linux') { /***/ }), -/* 219 */ +/* 220 */ /***/ (function(module, exports) { module.exports = require(undefined); /***/ }), -/* 220 */ +/* 221 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const isPlainObj = __webpack_require__(221); +const isPlainObj = __webpack_require__(222); module.exports = (obj, opts) => { if (!isPlainObj(obj)) { @@ -23265,7 +23416,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 221 */ +/* 222 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -23279,14 +23430,14 @@ module.exports = function (x) { /***/ }), -/* 222 */ +/* 223 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); const path = __webpack_require__(4); -const pify = __webpack_require__(223); +const pify = __webpack_require__(224); const semver = __webpack_require__(182); const defaults = { @@ -23425,7 +23576,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 223 */ +/* 224 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -23500,7 +23651,7 @@ module.exports = (input, options) => { /***/ }), -/* 224 */ +/* 225 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -23629,7 +23780,7 @@ module.exports = str => { /***/ }), -/* 225 */ +/* 226 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -23638,7 +23789,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackage", function() { return runScriptInPackage; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackageStreaming", function() { return runScriptInPackageStreaming; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "yarnWorkspacesInfo", function() { return yarnWorkspacesInfo; }); -/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(226); +/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(227); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -23715,7 +23866,7 @@ async function yarnWorkspacesInfo(directory) { } /***/ }), -/* 226 */ +/* 227 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -23726,9 +23877,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var stream__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(stream__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(112); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(227); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(228); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(262); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(263); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(142); 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; } @@ -23814,23 +23965,23 @@ function spawnStreaming(command, args, opts, { } /***/ }), -/* 227 */ +/* 228 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const childProcess = __webpack_require__(228); -const crossSpawn = __webpack_require__(229); -const stripFinalNewline = __webpack_require__(242); -const npmRunPath = __webpack_require__(243); -const onetime = __webpack_require__(244); -const makeError = __webpack_require__(246); -const normalizeStdio = __webpack_require__(251); -const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(252); -const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(253); -const {mergePromise, getSpawnedPromise} = __webpack_require__(260); -const {joinCommand, parseCommand} = __webpack_require__(261); +const childProcess = __webpack_require__(229); +const crossSpawn = __webpack_require__(230); +const stripFinalNewline = __webpack_require__(243); +const npmRunPath = __webpack_require__(244); +const onetime = __webpack_require__(245); +const makeError = __webpack_require__(247); +const normalizeStdio = __webpack_require__(252); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(253); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(254); +const {mergePromise, getSpawnedPromise} = __webpack_require__(261); +const {joinCommand, parseCommand} = __webpack_require__(262); const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; @@ -24077,21 +24228,21 @@ module.exports.node = (scriptPath, args, options = {}) => { /***/ }), -/* 228 */ +/* 229 */ /***/ (function(module, exports) { module.exports = require("child_process"); /***/ }), -/* 229 */ +/* 230 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const cp = __webpack_require__(228); -const parse = __webpack_require__(230); -const enoent = __webpack_require__(241); +const cp = __webpack_require__(229); +const parse = __webpack_require__(231); +const enoent = __webpack_require__(242); function spawn(command, args, options) { // Parse the arguments @@ -24129,16 +24280,16 @@ module.exports._enoent = enoent; /***/ }), -/* 230 */ +/* 231 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const resolveCommand = __webpack_require__(231); -const escape = __webpack_require__(237); -const readShebang = __webpack_require__(238); +const resolveCommand = __webpack_require__(232); +const escape = __webpack_require__(238); +const readShebang = __webpack_require__(239); const isWin = process.platform === 'win32'; const isExecutableRegExp = /\.(?:com|exe)$/i; @@ -24227,15 +24378,15 @@ module.exports = parse; /***/ }), -/* 231 */ +/* 232 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const which = __webpack_require__(232); -const pathKey = __webpack_require__(236)(); +const which = __webpack_require__(233); +const pathKey = __webpack_require__(237)(); function resolveCommandAttempt(parsed, withoutPathExt) { const cwd = process.cwd(); @@ -24285,7 +24436,7 @@ module.exports = resolveCommand; /***/ }), -/* 232 */ +/* 233 */ /***/ (function(module, exports, __webpack_require__) { const isWindows = process.platform === 'win32' || @@ -24294,7 +24445,7 @@ const isWindows = process.platform === 'win32' || const path = __webpack_require__(4) const COLON = isWindows ? ';' : ':' -const isexe = __webpack_require__(233) +const isexe = __webpack_require__(234) const getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) @@ -24416,15 +24567,15 @@ which.sync = whichSync /***/ }), -/* 233 */ +/* 234 */ /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(132) var core if (process.platform === 'win32' || global.TESTING_WINDOWS) { - core = __webpack_require__(234) -} else { core = __webpack_require__(235) +} else { + core = __webpack_require__(236) } module.exports = isexe @@ -24479,7 +24630,7 @@ function sync (path, options) { /***/ }), -/* 234 */ +/* 235 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -24527,7 +24678,7 @@ function sync (path, options) { /***/ }), -/* 235 */ +/* 236 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -24574,7 +24725,7 @@ function checkMode (stat, options) { /***/ }), -/* 236 */ +/* 237 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24597,7 +24748,7 @@ module.exports.default = pathKey; /***/ }), -/* 237 */ +/* 238 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24649,14 +24800,14 @@ module.exports.argument = escapeArgument; /***/ }), -/* 238 */ +/* 239 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); -const shebangCommand = __webpack_require__(239); +const shebangCommand = __webpack_require__(240); function readShebang(command) { // Read the first 150 bytes from the file @@ -24679,12 +24830,12 @@ module.exports = readShebang; /***/ }), -/* 239 */ +/* 240 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const shebangRegex = __webpack_require__(240); +const shebangRegex = __webpack_require__(241); module.exports = (string = '') => { const match = string.match(shebangRegex); @@ -24705,7 +24856,7 @@ module.exports = (string = '') => { /***/ }), -/* 240 */ +/* 241 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24714,7 +24865,7 @@ module.exports = /^#!(.*)/; /***/ }), -/* 241 */ +/* 242 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24780,7 +24931,7 @@ module.exports = { /***/ }), -/* 242 */ +/* 243 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24803,13 +24954,13 @@ module.exports = input => { /***/ }), -/* 243 */ +/* 244 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathKey = __webpack_require__(236); +const pathKey = __webpack_require__(237); const npmRunPath = options => { options = { @@ -24857,12 +25008,12 @@ module.exports.env = options => { /***/ }), -/* 244 */ +/* 245 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(245); +const mimicFn = __webpack_require__(246); const calledFunctions = new WeakMap(); @@ -24914,7 +25065,7 @@ module.exports.callCount = fn => { /***/ }), -/* 245 */ +/* 246 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -24934,12 +25085,12 @@ module.exports.default = mimicFn; /***/ }), -/* 246 */ +/* 247 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {signalsByName} = __webpack_require__(247); +const {signalsByName} = __webpack_require__(248); const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { if (timedOut) { @@ -25027,14 +25178,14 @@ module.exports = makeError; /***/ }), -/* 247 */ +/* 248 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(121); -var _signals=__webpack_require__(248); -var _realtime=__webpack_require__(250); +var _signals=__webpack_require__(249); +var _realtime=__webpack_require__(251); @@ -25104,14 +25255,14 @@ const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumb //# sourceMappingURL=main.js.map /***/ }), -/* 248 */ +/* 249 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(121); -var _core=__webpack_require__(249); -var _realtime=__webpack_require__(250); +var _core=__webpack_require__(250); +var _realtime=__webpack_require__(251); @@ -25145,7 +25296,7 @@ return{name,number,description,supported,action,forced,standard}; //# sourceMappingURL=signals.js.map /***/ }), -/* 249 */ +/* 250 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25424,7 +25575,7 @@ standard:"other"}];exports.SIGNALS=SIGNALS; //# sourceMappingURL=core.js.map /***/ }), -/* 250 */ +/* 251 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25449,7 +25600,7 @@ const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; //# sourceMappingURL=realtime.js.map /***/ }), -/* 251 */ +/* 252 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25508,13 +25659,13 @@ module.exports.node = opts => { /***/ }), -/* 252 */ +/* 253 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const os = __webpack_require__(121); -const onExit = __webpack_require__(217); +const onExit = __webpack_require__(218); const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; @@ -25627,14 +25778,14 @@ module.exports = { /***/ }), -/* 253 */ +/* 254 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const isStream = __webpack_require__(254); -const getStream = __webpack_require__(255); -const mergeStream = __webpack_require__(259); +const isStream = __webpack_require__(255); +const getStream = __webpack_require__(256); +const mergeStream = __webpack_require__(260); // `input` option const handleInput = (spawned, input) => { @@ -25731,7 +25882,7 @@ module.exports = { /***/ }), -/* 254 */ +/* 255 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25767,13 +25918,13 @@ module.exports = isStream; /***/ }), -/* 255 */ +/* 256 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pump = __webpack_require__(256); -const bufferStream = __webpack_require__(258); +const pump = __webpack_require__(257); +const bufferStream = __webpack_require__(259); class MaxBufferError extends Error { constructor() { @@ -25832,11 +25983,11 @@ module.exports.MaxBufferError = MaxBufferError; /***/ }), -/* 256 */ +/* 257 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(160) -var eos = __webpack_require__(257) +var eos = __webpack_require__(258) var fs = __webpack_require__(132) // we only need fs to get the ReadStream and WriteStream prototypes var noop = function () {} @@ -25920,7 +26071,7 @@ module.exports = pump /***/ }), -/* 257 */ +/* 258 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(160); @@ -26013,7 +26164,7 @@ module.exports = eos; /***/ }), -/* 258 */ +/* 259 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26072,7 +26223,7 @@ module.exports = options => { /***/ }), -/* 259 */ +/* 260 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26120,7 +26271,7 @@ module.exports = function (/*streams...*/) { /***/ }), -/* 260 */ +/* 261 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26173,7 +26324,7 @@ module.exports = { /***/ }), -/* 261 */ +/* 262 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26218,7 +26369,7 @@ module.exports = { /***/ }), -/* 262 */ +/* 263 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -26226,12 +26377,12 @@ module.exports = { // This file is licensed under the Apache License 2.0. // License text available at https://opensource.org/licenses/Apache-2.0 -module.exports = __webpack_require__(263); -module.exports.cli = __webpack_require__(267); +module.exports = __webpack_require__(264); +module.exports.cli = __webpack_require__(268); /***/ }), -/* 263 */ +/* 264 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26246,9 +26397,9 @@ var stream = __webpack_require__(136); var util = __webpack_require__(111); var fs = __webpack_require__(132); -var through = __webpack_require__(264); -var duplexer = __webpack_require__(265); -var StringDecoder = __webpack_require__(266).StringDecoder; +var through = __webpack_require__(265); +var duplexer = __webpack_require__(266); +var StringDecoder = __webpack_require__(267).StringDecoder; module.exports = Logger; @@ -26437,7 +26588,7 @@ function lineMerger(host) { /***/ }), -/* 264 */ +/* 265 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(136) @@ -26551,7 +26702,7 @@ function through (write, end, opts) { /***/ }), -/* 265 */ +/* 266 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(136) @@ -26644,13 +26795,13 @@ function duplex(writer, reader) { /***/ }), -/* 266 */ +/* 267 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 267 */ +/* 268 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26661,11 +26812,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(268); +var minimist = __webpack_require__(269); var path = __webpack_require__(4); -var Logger = __webpack_require__(263); -var pkg = __webpack_require__(269); +var Logger = __webpack_require__(264); +var pkg = __webpack_require__(270); module.exports = cli; @@ -26719,7 +26870,7 @@ function usage($0, p) { /***/ }), -/* 268 */ +/* 269 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -26961,13 +27112,13 @@ function isNumber (x) { /***/ }), -/* 269 */ +/* 270 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); /***/ }), -/* 270 */ +/* 271 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -26980,7 +27131,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(111); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(271); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(272); /* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(129); /* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(163); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(144); @@ -27075,7 +27226,7 @@ function packagesFromGlobPattern({ } /***/ }), -/* 271 */ +/* 272 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27145,7 +27296,7 @@ function getProjectPaths({ } /***/ }), -/* 272 */ +/* 273 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27153,13 +27304,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getAllChecksums", function() { return getAllChecksums; }); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(132); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(273); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(274); /* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(111); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(227); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(228); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(274); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(275); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -27388,19 +27539,19 @@ async function getAllChecksums(kbn, log) { } /***/ }), -/* 273 */ +/* 274 */ /***/ (function(module, exports) { module.exports = require("crypto"); /***/ }), -/* 274 */ +/* 275 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readYarnLock", function() { return readYarnLock; }); -/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(129); /* @@ -27444,7 +27595,7 @@ async function readYarnLock(kbn) { } /***/ }), -/* 275 */ +/* 276 */ /***/ (function(module, exports, __webpack_require__) { module.exports = @@ -29003,7 +29154,7 @@ module.exports = invariant; /* 9 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(273); +module.exports = __webpack_require__(274); /***/ }), /* 10 */, @@ -31327,7 +31478,7 @@ function onceStrict (fn) { /* 63 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(276); +module.exports = __webpack_require__(277); /***/ }), /* 64 */, @@ -32265,7 +32416,7 @@ module.exports.win32 = win32; /* 79 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(277); +module.exports = __webpack_require__(278); /***/ }), /* 80 */, @@ -37722,19 +37873,19 @@ module.exports = process && support(supportLevel); /******/ ]); /***/ }), -/* 276 */ +/* 277 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 277 */ +/* 278 */ /***/ (function(module, exports) { module.exports = require("tty"); /***/ }), -/* 278 */ +/* 279 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -37831,15 +37982,15 @@ class BootstrapCacheFile { } /***/ }), -/* 279 */ +/* 280 */ /***/ (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__(280); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(281); /* 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__(373); /* 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__); @@ -37939,21 +38090,21 @@ const CleanCommand = { }; /***/ }), -/* 280 */ +/* 281 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(111); const path = __webpack_require__(4); -const globby = __webpack_require__(281); -const isGlob = __webpack_require__(364); -const slash = __webpack_require__(362); +const globby = __webpack_require__(282); +const isGlob = __webpack_require__(365); +const slash = __webpack_require__(363); const gracefulFs = __webpack_require__(131); -const isPathCwd = __webpack_require__(365); -const isPathInside = __webpack_require__(366); -const rimraf = __webpack_require__(367); -const pMap = __webpack_require__(368); +const isPathCwd = __webpack_require__(366); +const isPathInside = __webpack_require__(367); +const rimraf = __webpack_require__(368); +const pMap = __webpack_require__(369); const rimrafP = promisify(rimraf); @@ -38067,19 +38218,19 @@ module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options /***/ }), -/* 281 */ +/* 282 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); -const arrayUnion = __webpack_require__(282); -const merge2 = __webpack_require__(283); -const glob = __webpack_require__(284); -const fastGlob = __webpack_require__(289); -const dirGlob = __webpack_require__(358); -const gitignore = __webpack_require__(360); -const {FilterStream, UniqueStream} = __webpack_require__(363); +const arrayUnion = __webpack_require__(283); +const merge2 = __webpack_require__(284); +const glob = __webpack_require__(285); +const fastGlob = __webpack_require__(290); +const dirGlob = __webpack_require__(359); +const gitignore = __webpack_require__(361); +const {FilterStream, UniqueStream} = __webpack_require__(364); const DEFAULT_FILTER = () => false; @@ -38252,7 +38403,7 @@ module.exports.gitignore = gitignore; /***/ }), -/* 282 */ +/* 283 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38264,7 +38415,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 283 */ +/* 284 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38415,7 +38566,7 @@ function pauseStreams (streams, options) { /***/ }), -/* 284 */ +/* 285 */ /***/ (function(module, exports, __webpack_require__) { // Approach: @@ -38464,13 +38615,13 @@ var fs = __webpack_require__(132) var rp = __webpack_require__(146) var minimatch = __webpack_require__(148) var Minimatch = minimatch.Minimatch -var inherits = __webpack_require__(285) +var inherits = __webpack_require__(286) var EE = __webpack_require__(154).EventEmitter var path = __webpack_require__(4) var assert = __webpack_require__(138) var isAbsolute = __webpack_require__(155) -var globSync = __webpack_require__(287) -var common = __webpack_require__(288) +var globSync = __webpack_require__(288) +var common = __webpack_require__(289) var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts @@ -39211,7 +39362,7 @@ Glob.prototype._stat2 = function (f, abs, er, stat, cb) { /***/ }), -/* 285 */ +/* 286 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -39221,12 +39372,12 @@ try { module.exports = util.inherits; } catch (e) { /* istanbul ignore next */ - module.exports = __webpack_require__(286); + module.exports = __webpack_require__(287); } /***/ }), -/* 286 */ +/* 287 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -39259,7 +39410,7 @@ if (typeof Object.create === 'function') { /***/ }), -/* 287 */ +/* 288 */ /***/ (function(module, exports, __webpack_require__) { module.exports = globSync @@ -39269,12 +39420,12 @@ var fs = __webpack_require__(132) var rp = __webpack_require__(146) var minimatch = __webpack_require__(148) var Minimatch = minimatch.Minimatch -var Glob = __webpack_require__(284).Glob +var Glob = __webpack_require__(285).Glob var util = __webpack_require__(111) var path = __webpack_require__(4) var assert = __webpack_require__(138) var isAbsolute = __webpack_require__(155) -var common = __webpack_require__(288) +var common = __webpack_require__(289) var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts @@ -39751,7 +39902,7 @@ GlobSync.prototype._makeAbs = function (f) { /***/ }), -/* 288 */ +/* 289 */ /***/ (function(module, exports, __webpack_require__) { exports.alphasort = alphasort @@ -39997,17 +40148,17 @@ function childrenIgnored (self, path) { /***/ }), -/* 289 */ +/* 290 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(290); -const async_1 = __webpack_require__(319); -const stream_1 = __webpack_require__(354); -const sync_1 = __webpack_require__(355); -const settings_1 = __webpack_require__(357); -const utils = __webpack_require__(291); +const taskManager = __webpack_require__(291); +const async_1 = __webpack_require__(320); +const stream_1 = __webpack_require__(355); +const sync_1 = __webpack_require__(356); +const settings_1 = __webpack_require__(358); +const utils = __webpack_require__(292); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -40071,13 +40222,13 @@ module.exports = FastGlob; /***/ }), -/* 290 */ +/* 291 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); +const utils = __webpack_require__(292); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -40142,30 +40293,30 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 291 */ +/* 292 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const array = __webpack_require__(292); +const array = __webpack_require__(293); exports.array = array; -const errno = __webpack_require__(293); +const errno = __webpack_require__(294); exports.errno = errno; -const fs = __webpack_require__(294); +const fs = __webpack_require__(295); exports.fs = fs; -const path = __webpack_require__(295); +const path = __webpack_require__(296); exports.path = path; -const pattern = __webpack_require__(296); +const pattern = __webpack_require__(297); exports.pattern = pattern; -const stream = __webpack_require__(317); +const stream = __webpack_require__(318); exports.stream = stream; -const string = __webpack_require__(318); +const string = __webpack_require__(319); exports.string = string; /***/ }), -/* 292 */ +/* 293 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40193,7 +40344,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 293 */ +/* 294 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40206,7 +40357,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 294 */ +/* 295 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40231,7 +40382,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 295 */ +/* 296 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40270,16 +40421,16 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 296 */ +/* 297 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const globParent = __webpack_require__(297); -const micromatch = __webpack_require__(300); -const picomatch = __webpack_require__(311); +const globParent = __webpack_require__(298); +const micromatch = __webpack_require__(301); +const picomatch = __webpack_require__(312); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/; @@ -40389,13 +40540,13 @@ exports.matchAny = matchAny; /***/ }), -/* 297 */ +/* 298 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isGlob = __webpack_require__(298); +var isGlob = __webpack_require__(299); var pathPosixDirname = __webpack_require__(4).posix.dirname; var isWin32 = __webpack_require__(121).platform() === 'win32'; @@ -40437,7 +40588,7 @@ module.exports = function globParent(str, opts) { /***/ }), -/* 298 */ +/* 299 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -40447,7 +40598,7 @@ module.exports = function globParent(str, opts) { * Released under the MIT License. */ -var isExtglob = __webpack_require__(299); +var isExtglob = __webpack_require__(300); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -40491,7 +40642,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 299 */ +/* 300 */ /***/ (function(module, exports) { /*! @@ -40517,16 +40668,16 @@ module.exports = function isExtglob(str) { /***/ }), -/* 300 */ +/* 301 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(111); -const braces = __webpack_require__(301); -const picomatch = __webpack_require__(311); -const utils = __webpack_require__(314); +const braces = __webpack_require__(302); +const picomatch = __webpack_require__(312); +const utils = __webpack_require__(315); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); /** @@ -40991,16 +41142,16 @@ module.exports = micromatch; /***/ }), -/* 301 */ +/* 302 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(302); -const compile = __webpack_require__(304); -const expand = __webpack_require__(308); -const parse = __webpack_require__(309); +const stringify = __webpack_require__(303); +const compile = __webpack_require__(305); +const expand = __webpack_require__(309); +const parse = __webpack_require__(310); /** * Expand the given pattern or create a regex-compatible string. @@ -41168,13 +41319,13 @@ module.exports = braces; /***/ }), -/* 302 */ +/* 303 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(303); +const utils = __webpack_require__(304); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -41207,7 +41358,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 303 */ +/* 304 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41326,14 +41477,14 @@ exports.flatten = (...args) => { /***/ }), -/* 304 */ +/* 305 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(305); -const utils = __webpack_require__(303); +const fill = __webpack_require__(306); +const utils = __webpack_require__(304); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -41390,7 +41541,7 @@ module.exports = compile; /***/ }), -/* 305 */ +/* 306 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41404,7 +41555,7 @@ module.exports = compile; const util = __webpack_require__(111); -const toRegexRange = __webpack_require__(306); +const toRegexRange = __webpack_require__(307); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -41646,7 +41797,7 @@ module.exports = fill; /***/ }), -/* 306 */ +/* 307 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41659,7 +41810,7 @@ module.exports = fill; -const isNumber = __webpack_require__(307); +const isNumber = __webpack_require__(308); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -41941,7 +42092,7 @@ module.exports = toRegexRange; /***/ }), -/* 307 */ +/* 308 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41966,15 +42117,15 @@ module.exports = function(num) { /***/ }), -/* 308 */ +/* 309 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(305); -const stringify = __webpack_require__(302); -const utils = __webpack_require__(303); +const fill = __webpack_require__(306); +const stringify = __webpack_require__(303); +const utils = __webpack_require__(304); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -42086,13 +42237,13 @@ module.exports = expand; /***/ }), -/* 309 */ +/* 310 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(302); +const stringify = __webpack_require__(303); /** * Constants @@ -42114,7 +42265,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(310); +} = __webpack_require__(311); /** * parse @@ -42426,7 +42577,7 @@ module.exports = parse; /***/ }), -/* 310 */ +/* 311 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42490,27 +42641,27 @@ module.exports = { /***/ }), -/* 311 */ +/* 312 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(312); +module.exports = __webpack_require__(313); /***/ }), -/* 312 */ +/* 313 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const scan = __webpack_require__(313); -const parse = __webpack_require__(316); -const utils = __webpack_require__(314); -const constants = __webpack_require__(315); +const scan = __webpack_require__(314); +const parse = __webpack_require__(317); +const utils = __webpack_require__(315); +const constants = __webpack_require__(316); const isObject = val => val && typeof val === 'object' && !Array.isArray(val); /** @@ -42846,13 +42997,13 @@ module.exports = picomatch; /***/ }), -/* 313 */ +/* 314 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(314); +const utils = __webpack_require__(315); const { CHAR_ASTERISK, /* * */ CHAR_AT, /* @ */ @@ -42869,7 +43020,7 @@ const { CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_RIGHT_SQUARE_BRACKET /* ] */ -} = __webpack_require__(315); +} = __webpack_require__(316); const isPathSeparator = code => { return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; @@ -43236,7 +43387,7 @@ module.exports = scan; /***/ }), -/* 314 */ +/* 315 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43249,7 +43400,7 @@ const { REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL -} = __webpack_require__(315); +} = __webpack_require__(316); exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); @@ -43307,7 +43458,7 @@ exports.wrapOutput = (input, state = {}, options = {}) => { /***/ }), -/* 315 */ +/* 316 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43493,14 +43644,14 @@ module.exports = { /***/ }), -/* 316 */ +/* 317 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const constants = __webpack_require__(315); -const utils = __webpack_require__(314); +const constants = __webpack_require__(316); +const utils = __webpack_require__(315); /** * Constants @@ -44578,13 +44729,13 @@ module.exports = parse; /***/ }), -/* 317 */ +/* 318 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const merge2 = __webpack_require__(283); +const merge2 = __webpack_require__(284); function merge(streams) { const mergedStream = merge2(streams); streams.forEach((stream) => { @@ -44601,7 +44752,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 318 */ +/* 319 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44618,14 +44769,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 319 */ +/* 320 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(320); -const provider_1 = __webpack_require__(347); +const stream_1 = __webpack_require__(321); +const provider_1 = __webpack_require__(348); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -44653,16 +44804,16 @@ exports.default = ProviderAsync; /***/ }), -/* 320 */ +/* 321 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(136); -const fsStat = __webpack_require__(321); -const fsWalk = __webpack_require__(326); -const reader_1 = __webpack_require__(346); +const fsStat = __webpack_require__(322); +const fsWalk = __webpack_require__(327); +const reader_1 = __webpack_require__(347); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -44715,15 +44866,15 @@ exports.default = ReaderStream; /***/ }), -/* 321 */ +/* 322 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(322); -const sync = __webpack_require__(323); -const settings_1 = __webpack_require__(324); +const async = __webpack_require__(323); +const sync = __webpack_require__(324); +const settings_1 = __webpack_require__(325); exports.Settings = settings_1.default; function stat(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -44746,7 +44897,7 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 322 */ +/* 323 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44784,7 +44935,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 323 */ +/* 324 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44813,13 +44964,13 @@ exports.read = read; /***/ }), -/* 324 */ +/* 325 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(325); +const fs = __webpack_require__(326); class Settings { constructor(_options = {}) { this._options = _options; @@ -44836,7 +44987,7 @@ exports.default = Settings; /***/ }), -/* 325 */ +/* 326 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44859,16 +45010,16 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 326 */ +/* 327 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(327); -const stream_1 = __webpack_require__(342); -const sync_1 = __webpack_require__(343); -const settings_1 = __webpack_require__(345); +const async_1 = __webpack_require__(328); +const stream_1 = __webpack_require__(343); +const sync_1 = __webpack_require__(344); +const settings_1 = __webpack_require__(346); exports.Settings = settings_1.default; function walk(directory, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -44898,13 +45049,13 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 327 */ +/* 328 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(328); +const async_1 = __webpack_require__(329); class AsyncProvider { constructor(_root, _settings) { this._root = _root; @@ -44935,17 +45086,17 @@ function callSuccessCallback(callback, entries) { /***/ }), -/* 328 */ +/* 329 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = __webpack_require__(154); -const fsScandir = __webpack_require__(329); -const fastq = __webpack_require__(338); -const common = __webpack_require__(340); -const reader_1 = __webpack_require__(341); +const fsScandir = __webpack_require__(330); +const fastq = __webpack_require__(339); +const common = __webpack_require__(341); +const reader_1 = __webpack_require__(342); class AsyncReader extends reader_1.default { constructor(_root, _settings) { super(_root, _settings); @@ -45035,15 +45186,15 @@ exports.default = AsyncReader; /***/ }), -/* 329 */ +/* 330 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(330); -const sync = __webpack_require__(335); -const settings_1 = __webpack_require__(336); +const async = __webpack_require__(331); +const sync = __webpack_require__(336); +const settings_1 = __webpack_require__(337); exports.Settings = settings_1.default; function scandir(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -45066,16 +45217,16 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 330 */ +/* 331 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(321); -const rpl = __webpack_require__(331); -const constants_1 = __webpack_require__(332); -const utils = __webpack_require__(333); +const fsStat = __webpack_require__(322); +const rpl = __webpack_require__(332); +const constants_1 = __webpack_require__(333); +const utils = __webpack_require__(334); function read(directory, settings, callback) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings, callback); @@ -45163,7 +45314,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 331 */ +/* 332 */ /***/ (function(module, exports) { module.exports = runParallel @@ -45217,7 +45368,7 @@ function runParallel (tasks, cb) { /***/ }), -/* 332 */ +/* 333 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45237,18 +45388,18 @@ exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = IS_MATCHED_BY_MAJOR || IS_MATCHED_B /***/ }), -/* 333 */ +/* 334 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(334); +const fs = __webpack_require__(335); exports.fs = fs; /***/ }), -/* 334 */ +/* 335 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45273,15 +45424,15 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 335 */ +/* 336 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(321); -const constants_1 = __webpack_require__(332); -const utils = __webpack_require__(333); +const fsStat = __webpack_require__(322); +const constants_1 = __webpack_require__(333); +const utils = __webpack_require__(334); function read(directory, settings) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings); @@ -45332,15 +45483,15 @@ exports.readdir = readdir; /***/ }), -/* 336 */ +/* 337 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(321); -const fs = __webpack_require__(337); +const fsStat = __webpack_require__(322); +const fs = __webpack_require__(338); class Settings { constructor(_options = {}) { this._options = _options; @@ -45363,7 +45514,7 @@ exports.default = Settings; /***/ }), -/* 337 */ +/* 338 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45388,13 +45539,13 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 338 */ +/* 339 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var reusify = __webpack_require__(339) +var reusify = __webpack_require__(340) function fastqueue (context, worker, concurrency) { if (typeof context === 'function') { @@ -45568,7 +45719,7 @@ module.exports = fastqueue /***/ }), -/* 339 */ +/* 340 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45608,7 +45759,7 @@ module.exports = reusify /***/ }), -/* 340 */ +/* 341 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45639,13 +45790,13 @@ exports.joinPathSegments = joinPathSegments; /***/ }), -/* 341 */ +/* 342 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const common = __webpack_require__(340); +const common = __webpack_require__(341); class Reader { constructor(_root, _settings) { this._root = _root; @@ -45657,14 +45808,14 @@ exports.default = Reader; /***/ }), -/* 342 */ +/* 343 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(136); -const async_1 = __webpack_require__(328); +const async_1 = __webpack_require__(329); class StreamProvider { constructor(_root, _settings) { this._root = _root; @@ -45694,13 +45845,13 @@ exports.default = StreamProvider; /***/ }), -/* 343 */ +/* 344 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(344); +const sync_1 = __webpack_require__(345); class SyncProvider { constructor(_root, _settings) { this._root = _root; @@ -45715,15 +45866,15 @@ exports.default = SyncProvider; /***/ }), -/* 344 */ +/* 345 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsScandir = __webpack_require__(329); -const common = __webpack_require__(340); -const reader_1 = __webpack_require__(341); +const fsScandir = __webpack_require__(330); +const common = __webpack_require__(341); +const reader_1 = __webpack_require__(342); class SyncReader extends reader_1.default { constructor() { super(...arguments); @@ -45781,14 +45932,14 @@ exports.default = SyncReader; /***/ }), -/* 345 */ +/* 346 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsScandir = __webpack_require__(329); +const fsScandir = __webpack_require__(330); class Settings { constructor(_options = {}) { this._options = _options; @@ -45814,15 +45965,15 @@ exports.default = Settings; /***/ }), -/* 346 */ +/* 347 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(321); -const utils = __webpack_require__(291); +const fsStat = __webpack_require__(322); +const utils = __webpack_require__(292); class Reader { constructor(_settings) { this._settings = _settings; @@ -45854,17 +46005,17 @@ exports.default = Reader; /***/ }), -/* 347 */ +/* 348 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(348); -const entry_1 = __webpack_require__(351); -const error_1 = __webpack_require__(352); -const entry_2 = __webpack_require__(353); +const deep_1 = __webpack_require__(349); +const entry_1 = __webpack_require__(352); +const error_1 = __webpack_require__(353); +const entry_2 = __webpack_require__(354); class Provider { constructor(_settings) { this._settings = _settings; @@ -45909,14 +46060,14 @@ exports.default = Provider; /***/ }), -/* 348 */ +/* 349 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); -const partial_1 = __webpack_require__(349); +const utils = __webpack_require__(292); +const partial_1 = __webpack_require__(350); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -45970,13 +46121,13 @@ exports.default = DeepFilter; /***/ }), -/* 349 */ +/* 350 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(350); +const matcher_1 = __webpack_require__(351); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -46015,13 +46166,13 @@ exports.default = PartialMatcher; /***/ }), -/* 350 */ +/* 351 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); +const utils = __webpack_require__(292); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -46072,13 +46223,13 @@ exports.default = Matcher; /***/ }), -/* 351 */ +/* 352 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); +const utils = __webpack_require__(292); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -46134,13 +46285,13 @@ exports.default = EntryFilter; /***/ }), -/* 352 */ +/* 353 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); +const utils = __webpack_require__(292); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -46156,13 +46307,13 @@ exports.default = ErrorFilter; /***/ }), -/* 353 */ +/* 354 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(291); +const utils = __webpack_require__(292); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -46189,15 +46340,15 @@ exports.default = EntryTransformer; /***/ }), -/* 354 */ +/* 355 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(136); -const stream_2 = __webpack_require__(320); -const provider_1 = __webpack_require__(347); +const stream_2 = __webpack_require__(321); +const provider_1 = __webpack_require__(348); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -46227,14 +46378,14 @@ exports.default = ProviderStream; /***/ }), -/* 355 */ +/* 356 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(356); -const provider_1 = __webpack_require__(347); +const sync_1 = __webpack_require__(357); +const provider_1 = __webpack_require__(348); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -46257,15 +46408,15 @@ exports.default = ProviderSync; /***/ }), -/* 356 */ +/* 357 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(321); -const fsWalk = __webpack_require__(326); -const reader_1 = __webpack_require__(346); +const fsStat = __webpack_require__(322); +const fsWalk = __webpack_require__(327); +const reader_1 = __webpack_require__(347); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -46307,7 +46458,7 @@ exports.default = ReaderSync; /***/ }), -/* 357 */ +/* 358 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46366,13 +46517,13 @@ exports.default = Settings; /***/ }), -/* 358 */ +/* 359 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(359); +const pathType = __webpack_require__(360); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -46448,7 +46599,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 359 */ +/* 360 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46498,7 +46649,7 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 360 */ +/* 361 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46506,9 +46657,9 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); const {promisify} = __webpack_require__(111); const fs = __webpack_require__(132); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(289); -const gitIgnore = __webpack_require__(361); -const slash = __webpack_require__(362); +const fastGlob = __webpack_require__(290); +const gitIgnore = __webpack_require__(362); +const slash = __webpack_require__(363); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -46622,7 +46773,7 @@ module.exports.sync = options => { /***/ }), -/* 361 */ +/* 362 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -47225,7 +47376,7 @@ if ( /***/ }), -/* 362 */ +/* 363 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47243,7 +47394,7 @@ module.exports = path => { /***/ }), -/* 363 */ +/* 364 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47296,7 +47447,7 @@ module.exports = { /***/ }), -/* 364 */ +/* 365 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -47306,7 +47457,7 @@ module.exports = { * Released under the MIT License. */ -var isExtglob = __webpack_require__(299); +var isExtglob = __webpack_require__(300); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -47350,7 +47501,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 365 */ +/* 366 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47372,7 +47523,7 @@ module.exports = path_ => { /***/ }), -/* 366 */ +/* 367 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47400,7 +47551,7 @@ module.exports = (childPath, parentPath) => { /***/ }), -/* 367 */ +/* 368 */ /***/ (function(module, exports, __webpack_require__) { const assert = __webpack_require__(138) @@ -47408,7 +47559,7 @@ const path = __webpack_require__(4) const fs = __webpack_require__(132) let glob = undefined try { - glob = __webpack_require__(284) + glob = __webpack_require__(285) } catch (_err) { // treat glob as optional. } @@ -47774,12 +47925,12 @@ rimraf.sync = rimrafSync /***/ }), -/* 368 */ +/* 369 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(369); +const AggregateError = __webpack_require__(370); module.exports = async ( iterable, @@ -47862,13 +48013,13 @@ module.exports = async ( /***/ }), -/* 369 */ +/* 370 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const indentString = __webpack_require__(370); -const cleanStack = __webpack_require__(371); +const indentString = __webpack_require__(371); +const cleanStack = __webpack_require__(372); const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); @@ -47916,7 +48067,7 @@ module.exports = AggregateError; /***/ }), -/* 370 */ +/* 371 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47958,7 +48109,7 @@ module.exports = (string, count = 1, options) => { /***/ }), -/* 371 */ +/* 372 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48005,15 +48156,15 @@ module.exports = (stack, options) => { /***/ }), -/* 372 */ +/* 373 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(373); -const cliCursor = __webpack_require__(377); -const cliSpinners = __webpack_require__(381); -const logSymbols = __webpack_require__(383); +const chalk = __webpack_require__(374); +const cliCursor = __webpack_require__(378); +const cliSpinners = __webpack_require__(382); +const logSymbols = __webpack_require__(384); class Ora { constructor(options) { @@ -48160,16 +48311,16 @@ module.exports.promise = (action, options) => { /***/ }), -/* 373 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(113); -const ansiStyles = __webpack_require__(374); -const stdoutColor = __webpack_require__(375).stdout; +const ansiStyles = __webpack_require__(375); +const stdoutColor = __webpack_require__(376).stdout; -const template = __webpack_require__(376); +const template = __webpack_require__(377); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -48395,7 +48546,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 374 */ +/* 375 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48568,7 +48719,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 375 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48710,7 +48861,7 @@ module.exports = { /***/ }), -/* 376 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48845,12 +48996,12 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 377 */ +/* 378 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(378); +const restoreCursor = __webpack_require__(379); let hidden = false; @@ -48891,13 +49042,13 @@ exports.toggle = (force, stream) => { /***/ }), -/* 378 */ +/* 379 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(379); -const signalExit = __webpack_require__(217); +const onetime = __webpack_require__(380); +const signalExit = __webpack_require__(218); module.exports = onetime(() => { signalExit(() => { @@ -48907,12 +49058,12 @@ module.exports = onetime(() => { /***/ }), -/* 379 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(380); +const mimicFn = __webpack_require__(381); module.exports = (fn, opts) => { // TODO: Remove this in v3 @@ -48953,7 +49104,7 @@ module.exports = (fn, opts) => { /***/ }), -/* 380 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48969,27 +49120,27 @@ module.exports = (to, from) => { /***/ }), -/* 381 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(382); +module.exports = __webpack_require__(383); /***/ }), -/* 382 */ +/* 383 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 383 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(384); +const chalk = __webpack_require__(385); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -49011,16 +49162,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 384 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(113); -const ansiStyles = __webpack_require__(385); -const stdoutColor = __webpack_require__(386).stdout; +const ansiStyles = __webpack_require__(386); +const stdoutColor = __webpack_require__(387).stdout; -const template = __webpack_require__(387); +const template = __webpack_require__(388); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -49246,7 +49397,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 385 */ +/* 386 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49419,7 +49570,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 386 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49561,7 +49712,7 @@ module.exports = { /***/ }), -/* 387 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49696,7 +49847,7 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 388 */ +/* 389 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -49757,7 +49908,7 @@ const RunCommand = { }; /***/ }), -/* 389 */ +/* 390 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -49767,7 +49918,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(142); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(143); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(390); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(391); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -49852,14 +50003,14 @@ const WatchCommand = { }; /***/ }), -/* 390 */ +/* 391 */ /***/ (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__(391); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(392); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -49926,141 +50077,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 391 */ +/* 392 */ /***/ (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__(392); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(393); /* 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__(393); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(394); /* 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__(394); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(395); /* 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__(395); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(396); /* 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__(396); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(397); /* 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__(397); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(398); /* 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__(398); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(399); /* 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__(399); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(400); /* 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__(400); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(401); /* 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__(401); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(402); /* 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__(402); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(403); /* 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__(403); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(404); /* 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__(404); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(405); /* 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__(405); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(406); /* 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__(406); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(407); /* 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__(407); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(408); /* 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__(408); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(409); /* 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__(409); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(410); /* 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__(411); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(412); /* 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__(412); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(413); /* 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__(413); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(414); /* 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__(414); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(415); /* 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__(415); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(416); /* 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__(416); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(417); /* 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__(419); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(420); /* 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__(420); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(421); /* 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__(421); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(422); /* 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__(422); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(423); /* 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__(423); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(424); /* 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__(104); /* 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__(424); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(425); /* 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__(425); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(426); /* 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__(426); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(427); /* 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__(427); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(428); /* 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__(428); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(429); /* 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__(429); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(430); /* 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__(430); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(431); /* 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__(432); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(433); /* 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__(433); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(434); /* 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__(434); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(435); /* 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__(437); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(438); /* 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); @@ -50071,175 +50222,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(438); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(439); /* 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__(439); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(440); /* 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__(440); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(441); /* 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__(441); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(442); /* 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__(442); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(443); /* 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__(443); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(444); /* 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__(444); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(445); /* 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__(445); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(446); /* 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__(446); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(447); /* 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__(447); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(448); /* 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__(448); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(449); /* 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__(449); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(450); /* 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__(450); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(451); /* 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__(435); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(436); /* 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__(451); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(452); /* 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__(452); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(453); /* 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__(453); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(454); /* 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__(454); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(455); /* 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__(455); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(456); /* 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__(456); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(457); /* 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__(436); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(437); /* 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__(457); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(458); /* 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__(458); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(459); /* 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__(459); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(460); /* 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__(460); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(461); /* 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__(461); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(462); /* 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__(462); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(463); /* 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__(463); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(464); /* 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__(464); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(465); /* 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__(465); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(466); /* 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__(466); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(467); /* 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__(468); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(469); /* 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__(469); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(470); /* 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__(470); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(471); /* 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__(418); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(419); /* 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__(431); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(432); /* 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__(471); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(472); /* 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__(472); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(473); /* 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__(473); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(474); /* 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__(474); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(475); /* 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__(475); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(476); /* 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__(417); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(418); /* 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__(476); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(477); /* 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__(477); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(478); /* 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__(478); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(479); /* 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__(479); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(480); /* 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__(480); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(481); /* 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__(481); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(482); /* 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__(482); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(483); /* 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__(483); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(484); /* 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__(484); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(485); /* 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__(485); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(486); /* 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__(486); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(487); /* 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__(487); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(488); /* 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__(488); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(489); /* 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 */ @@ -50351,7 +50502,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 392 */ +/* 393 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50432,14 +50583,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 393 */ +/* 394 */ /***/ (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__(392); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(393); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(107); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -50455,7 +50606,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 394 */ +/* 395 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50504,7 +50655,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 395 */ +/* 396 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50605,7 +50756,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 396 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50766,7 +50917,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 397 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50886,7 +51037,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 398 */ +/* 399 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -50981,7 +51132,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 399 */ +/* 400 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51042,7 +51193,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 400 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51058,7 +51209,7 @@ function combineAll(project) { /***/ }), -/* 401 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51090,7 +51241,7 @@ function combineLatest() { /***/ }), -/* 402 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51110,7 +51261,7 @@ function concat() { /***/ }), -/* 403 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51126,13 +51277,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 404 */ +/* 405 */ /***/ (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__(403); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(404); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -51142,7 +51293,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 405 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51207,7 +51358,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 406 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51295,7 +51446,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 407 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51371,7 +51522,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 408 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51421,7 +51572,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 409 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51429,7 +51580,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__(410); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(411); /* 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 */ @@ -51528,7 +51679,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 410 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51542,7 +51693,7 @@ function isDate(value) { /***/ }), -/* 411 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51688,7 +51839,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 412 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51726,7 +51877,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 413 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51804,7 +51955,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 414 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51875,13 +52026,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 415 */ +/* 416 */ /***/ (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__(414); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(415); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -51891,7 +52042,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 416 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51899,9 +52050,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__(104); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(408); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(418); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(409); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(419); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -51923,7 +52074,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 417 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51989,7 +52140,7 @@ function defaultErrorFactory() { /***/ }), -/* 418 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52051,7 +52202,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 419 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52073,7 +52224,7 @@ function endWith() { /***/ }), -/* 420 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52135,7 +52286,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 421 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52192,7 +52343,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 422 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52289,7 +52440,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 423 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52408,7 +52559,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 424 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52446,7 +52597,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 425 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52518,13 +52669,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 426 */ +/* 427 */ /***/ (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__(425); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(426); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -52534,7 +52685,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 427 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52542,9 +52693,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__(104); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(408); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(417); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(409); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(418); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(60); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -52561,7 +52712,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 428 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52598,7 +52749,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 429 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52642,7 +52793,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 430 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52650,9 +52801,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__(104); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(431); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(408); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(432); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(409); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(60); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -52669,7 +52820,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 431 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52746,7 +52897,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 432 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52785,7 +52936,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52835,13 +52986,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 434 */ +/* 435 */ /***/ (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__(435); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(436); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -52854,15 +53005,15 @@ function max(comparer) { /***/ }), -/* 435 */ +/* 436 */ /***/ (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__(436); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(431); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(408); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(437); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(432); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(409); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -52883,7 +53034,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 436 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52965,7 +53116,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 437 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52985,7 +53136,7 @@ function merge() { /***/ }), -/* 438 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53010,7 +53161,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 439 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53122,13 +53273,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 440 */ +/* 441 */ /***/ (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__(435); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(436); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -53141,7 +53292,7 @@ function min(comparer) { /***/ }), -/* 441 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53190,7 +53341,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 442 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53281,7 +53432,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 443 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53329,7 +53480,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53352,7 +53503,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 445 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53392,14 +53543,14 @@ function plucker(props, length) { /***/ }), -/* 446 */ +/* 447 */ /***/ (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__(441); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(442); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -53412,14 +53563,14 @@ function publish(selector) { /***/ }), -/* 447 */ +/* 448 */ /***/ (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__(441); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(442); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -53430,14 +53581,14 @@ function publishBehavior(value) { /***/ }), -/* 448 */ +/* 449 */ /***/ (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__(441); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(442); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -53448,14 +53599,14 @@ function publishLast() { /***/ }), -/* 449 */ +/* 450 */ /***/ (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__(441); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(442); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -53471,7 +53622,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 450 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53498,7 +53649,7 @@ function race() { /***/ }), -/* 451 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53563,7 +53714,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 452 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53659,7 +53810,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 453 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53712,7 +53863,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 454 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53800,7 +53951,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 455 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53857,7 +54008,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 456 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53917,7 +54068,7 @@ function dispatchNotification(state) { /***/ }), -/* 457 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54040,13 +54191,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 458 */ +/* 459 */ /***/ (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__(441); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(442); /* 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 */ @@ -54063,7 +54214,7 @@ function share() { /***/ }), -/* 459 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54127,7 +54278,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 460 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54207,7 +54358,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 461 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54249,7 +54400,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 462 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54311,7 +54462,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 463 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54368,7 +54519,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 464 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54424,7 +54575,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 465 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54453,13 +54604,13 @@ function startWith() { /***/ }), -/* 466 */ +/* 467 */ /***/ (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__(467); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(468); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -54484,7 +54635,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 467 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54548,13 +54699,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 469 */ /***/ (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__(469); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(470); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(60); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -54566,7 +54717,7 @@ function switchAll() { /***/ }), -/* 469 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54657,13 +54808,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 470 */ +/* 471 */ /***/ (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__(469); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(470); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -54673,7 +54824,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 471 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54723,7 +54874,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 472 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54791,7 +54942,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 473 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54879,7 +55030,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 474 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54983,7 +55134,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54992,7 +55143,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__(474); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(475); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -55081,7 +55232,7 @@ function dispatchNext(arg) { /***/ }), -/* 476 */ +/* 477 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55089,7 +55240,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__(436); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(437); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -55125,7 +55276,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 477 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55133,7 +55284,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__(478); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); /* 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 */ @@ -55150,7 +55301,7 @@ function timeout(due, scheduler) { /***/ }), -/* 478 */ +/* 479 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55158,7 +55309,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__(410); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(411); /* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); /* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -55232,7 +55383,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 479 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55262,13 +55413,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 480 */ +/* 481 */ /***/ (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__(435); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(436); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -55285,7 +55436,7 @@ function toArray() { /***/ }), -/* 481 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55365,7 +55516,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55455,7 +55606,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55625,7 +55776,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55768,7 +55919,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55865,7 +56016,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55960,7 +56111,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55982,7 +56133,7 @@ function zip() { /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55998,7 +56149,7 @@ function zipAll(project) { /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56007,8 +56158,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(161); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(142); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(144); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(490); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(491); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(491); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(492); 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; } @@ -56090,7 +56241,7 @@ function toArray(value) { } /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56243,7 +56394,7 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56251,12 +56402,12 @@ __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__(492); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(493); /* 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__(496); +/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(497); /* 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 _projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(271); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(272); 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; } @@ -56397,15 +56548,15 @@ class Kibana { } /***/ }), -/* 492 */ +/* 493 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(148); -const arrayUnion = __webpack_require__(493); -const arrayDiffer = __webpack_require__(494); -const arrify = __webpack_require__(495); +const arrayUnion = __webpack_require__(494); +const arrayDiffer = __webpack_require__(495); +const arrify = __webpack_require__(496); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -56429,7 +56580,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 493 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56441,7 +56592,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 494 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56456,7 +56607,7 @@ module.exports = arrayDiffer; /***/ }), -/* 495 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56486,7 +56637,7 @@ module.exports = arrify; /***/ }), -/* 496 */ +/* 497 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56514,15 +56665,15 @@ module.exports = (childPath, parentPath) => { /***/ }), -/* 497 */ +/* 498 */ /***/ (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__(498); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(499); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(734); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(735); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -56547,19 +56698,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 498 */ +/* 499 */ /***/ (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__(499); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(500); /* 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__(280); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(281); /* 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__(271); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(272); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(129); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(142); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(163); @@ -56695,7 +56846,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 499 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56703,13 +56854,13 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(154); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pAll = __webpack_require__(500); -const arrify = __webpack_require__(502); -const globby = __webpack_require__(503); -const isGlob = __webpack_require__(718); -const cpFile = __webpack_require__(719); -const junk = __webpack_require__(731); -const CpyError = __webpack_require__(732); +const pAll = __webpack_require__(501); +const arrify = __webpack_require__(503); +const globby = __webpack_require__(504); +const isGlob = __webpack_require__(719); +const cpFile = __webpack_require__(720); +const junk = __webpack_require__(732); +const CpyError = __webpack_require__(733); const defaultOptions = { ignoreJunk: true @@ -56828,12 +56979,12 @@ module.exports = (source, destination, { /***/ }), -/* 500 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(501); +const pMap = __webpack_require__(502); module.exports = (iterable, options) => pMap(iterable, element => element(), options); // TODO: Remove this for the next major release @@ -56841,7 +56992,7 @@ module.exports.default = module.exports; /***/ }), -/* 501 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56920,7 +57071,7 @@ module.exports.default = pMap; /***/ }), -/* 502 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56950,17 +57101,17 @@ module.exports = arrify; /***/ }), -/* 503 */ +/* 504 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); -const arrayUnion = __webpack_require__(504); -const glob = __webpack_require__(506); -const fastGlob = __webpack_require__(511); -const dirGlob = __webpack_require__(711); -const gitignore = __webpack_require__(714); +const arrayUnion = __webpack_require__(505); +const glob = __webpack_require__(507); +const fastGlob = __webpack_require__(512); +const dirGlob = __webpack_require__(712); +const gitignore = __webpack_require__(715); const DEFAULT_FILTER = () => false; @@ -57105,12 +57256,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 504 */ +/* 505 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(505); +var arrayUniq = __webpack_require__(506); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -57118,7 +57269,7 @@ module.exports = function () { /***/ }), -/* 505 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57187,7 +57338,7 @@ if ('Set' in global) { /***/ }), -/* 506 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { // Approach: @@ -57236,13 +57387,13 @@ var fs = __webpack_require__(132) var rp = __webpack_require__(146) var minimatch = __webpack_require__(148) var Minimatch = minimatch.Minimatch -var inherits = __webpack_require__(507) +var inherits = __webpack_require__(508) var EE = __webpack_require__(154).EventEmitter var path = __webpack_require__(4) var assert = __webpack_require__(138) var isAbsolute = __webpack_require__(155) -var globSync = __webpack_require__(509) -var common = __webpack_require__(510) +var globSync = __webpack_require__(510) +var common = __webpack_require__(511) var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts @@ -57983,7 +58134,7 @@ Glob.prototype._stat2 = function (f, abs, er, stat, cb) { /***/ }), -/* 507 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -57993,12 +58144,12 @@ try { module.exports = util.inherits; } catch (e) { /* istanbul ignore next */ - module.exports = __webpack_require__(508); + module.exports = __webpack_require__(509); } /***/ }), -/* 508 */ +/* 509 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -58031,7 +58182,7 @@ if (typeof Object.create === 'function') { /***/ }), -/* 509 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { module.exports = globSync @@ -58041,12 +58192,12 @@ var fs = __webpack_require__(132) var rp = __webpack_require__(146) var minimatch = __webpack_require__(148) var Minimatch = minimatch.Minimatch -var Glob = __webpack_require__(506).Glob +var Glob = __webpack_require__(507).Glob var util = __webpack_require__(111) var path = __webpack_require__(4) var assert = __webpack_require__(138) var isAbsolute = __webpack_require__(155) -var common = __webpack_require__(510) +var common = __webpack_require__(511) var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts @@ -58523,7 +58674,7 @@ GlobSync.prototype._makeAbs = function (f) { /***/ }), -/* 510 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { exports.alphasort = alphasort @@ -58769,10 +58920,10 @@ function childrenIgnored (self, path) { /***/ }), -/* 511 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(512); +const pkg = __webpack_require__(513); module.exports = pkg.async; module.exports.default = pkg.async; @@ -58785,19 +58936,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 512 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(513); -var taskManager = __webpack_require__(514); -var reader_async_1 = __webpack_require__(682); -var reader_stream_1 = __webpack_require__(706); -var reader_sync_1 = __webpack_require__(707); -var arrayUtils = __webpack_require__(709); -var streamUtils = __webpack_require__(710); +var optionsManager = __webpack_require__(514); +var taskManager = __webpack_require__(515); +var reader_async_1 = __webpack_require__(683); +var reader_stream_1 = __webpack_require__(707); +var reader_sync_1 = __webpack_require__(708); +var arrayUtils = __webpack_require__(710); +var streamUtils = __webpack_require__(711); /** * Synchronous API. */ @@ -58863,7 +59014,7 @@ function isString(source) { /***/ }), -/* 513 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58901,13 +59052,13 @@ exports.prepare = prepare; /***/ }), -/* 514 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(515); +var patternUtils = __webpack_require__(516); /** * Generate tasks based on parent directory of each pattern. */ @@ -58998,16 +59149,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 515 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(516); -var isGlob = __webpack_require__(519); -var micromatch = __webpack_require__(520); +var globParent = __webpack_require__(517); +var isGlob = __webpack_require__(520); +var micromatch = __webpack_require__(521); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -59153,15 +59304,15 @@ exports.matchAny = matchAny; /***/ }), -/* 516 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(517); -var pathDirname = __webpack_require__(518); +var isglob = __webpack_require__(518); +var pathDirname = __webpack_require__(519); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -59184,7 +59335,7 @@ module.exports = function globParent(str) { /***/ }), -/* 517 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -59194,7 +59345,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(299); +var isExtglob = __webpack_require__(300); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -59215,7 +59366,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 518 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59365,7 +59516,7 @@ module.exports.win32 = win32; /***/ }), -/* 519 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -59375,7 +59526,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(299); +var isExtglob = __webpack_require__(300); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -59417,7 +59568,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 520 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59428,18 +59579,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(111); -var braces = __webpack_require__(521); -var toRegex = __webpack_require__(634); -var extend = __webpack_require__(642); +var braces = __webpack_require__(522); +var toRegex = __webpack_require__(635); +var extend = __webpack_require__(643); /** * Local dependencies */ -var compilers = __webpack_require__(645); -var parsers = __webpack_require__(678); -var cache = __webpack_require__(679); -var utils = __webpack_require__(680); +var compilers = __webpack_require__(646); +var parsers = __webpack_require__(679); +var cache = __webpack_require__(680); +var utils = __webpack_require__(681); var MAX_LENGTH = 1024 * 64; /** @@ -60301,7 +60452,7 @@ module.exports = micromatch; /***/ }), -/* 521 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60311,18 +60462,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(522); -var unique = __webpack_require__(536); -var extend = __webpack_require__(531); +var toRegex = __webpack_require__(523); +var unique = __webpack_require__(537); +var extend = __webpack_require__(532); /** * Local dependencies */ -var compilers = __webpack_require__(537); -var parsers = __webpack_require__(554); -var Braces = __webpack_require__(564); -var utils = __webpack_require__(538); +var compilers = __webpack_require__(538); +var parsers = __webpack_require__(555); +var Braces = __webpack_require__(565); +var utils = __webpack_require__(539); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -60626,15 +60777,15 @@ module.exports = braces; /***/ }), -/* 522 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(523); -var extend = __webpack_require__(531); -var not = __webpack_require__(533); +var define = __webpack_require__(524); +var extend = __webpack_require__(532); +var not = __webpack_require__(534); var MAX_LENGTH = 1024 * 64; /** @@ -60781,7 +60932,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 523 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60794,7 +60945,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(524); +var isDescriptor = __webpack_require__(525); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -60819,7 +60970,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 524 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60832,9 +60983,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(525); -var isAccessor = __webpack_require__(526); -var isData = __webpack_require__(529); +var typeOf = __webpack_require__(526); +var isAccessor = __webpack_require__(527); +var isData = __webpack_require__(530); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -60848,7 +60999,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 525 */ +/* 526 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61001,7 +61152,7 @@ function isBuffer(val) { /***/ }), -/* 526 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61014,7 +61165,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(527); +var typeOf = __webpack_require__(528); // accessor descriptor properties var accessor = { @@ -61077,10 +61228,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 527 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -61199,7 +61350,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 528 */ +/* 529 */ /***/ (function(module, exports) { /*! @@ -61226,7 +61377,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 529 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61239,7 +61390,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(530); +var typeOf = __webpack_require__(531); // data descriptor properties var data = { @@ -61288,10 +61439,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 530 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -61410,13 +61561,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 531 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(533); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -61450,7 +61601,7 @@ function hasOwn(obj, key) { /***/ }), -/* 532 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61470,13 +61621,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 533 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(534); +var extend = __webpack_require__(535); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -61543,13 +61694,13 @@ module.exports = toRegex; /***/ }), -/* 534 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(535); +var isObject = __webpack_require__(536); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -61583,7 +61734,7 @@ function hasOwn(obj, key) { /***/ }), -/* 535 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61603,7 +61754,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 536 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61653,13 +61804,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 537 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(538); +var utils = __webpack_require__(539); module.exports = function(braces, options) { braces.compiler @@ -61942,25 +62093,25 @@ function hasQueue(node) { /***/ }), -/* 538 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(539); +var splitString = __webpack_require__(540); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(531); -utils.flatten = __webpack_require__(545); -utils.isObject = __webpack_require__(543); -utils.fillRange = __webpack_require__(546); -utils.repeat = __webpack_require__(553); -utils.unique = __webpack_require__(536); +utils.extend = __webpack_require__(532); +utils.flatten = __webpack_require__(546); +utils.isObject = __webpack_require__(544); +utils.fillRange = __webpack_require__(547); +utils.repeat = __webpack_require__(554); +utils.unique = __webpack_require__(537); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -62292,7 +62443,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 539 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62305,7 +62456,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(540); +var extend = __webpack_require__(541); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -62470,14 +62621,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 540 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(541); -var assignSymbols = __webpack_require__(544); +var isExtendable = __webpack_require__(542); +var assignSymbols = __webpack_require__(545); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -62537,7 +62688,7 @@ function isEnum(obj, key) { /***/ }), -/* 541 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62550,7 +62701,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(542); +var isPlainObject = __webpack_require__(543); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -62558,7 +62709,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 542 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62571,7 +62722,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(543); +var isObject = __webpack_require__(544); function isObjectObject(o) { return isObject(o) === true @@ -62602,7 +62753,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 543 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62621,7 +62772,7 @@ module.exports = function isObject(val) { /***/ }), -/* 544 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62668,7 +62819,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 545 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62697,7 +62848,7 @@ function flat(arr, res) { /***/ }), -/* 546 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62711,10 +62862,10 @@ function flat(arr, res) { var util = __webpack_require__(111); -var isNumber = __webpack_require__(547); -var extend = __webpack_require__(549); -var repeat = __webpack_require__(551); -var toRegex = __webpack_require__(552); +var isNumber = __webpack_require__(548); +var extend = __webpack_require__(550); +var repeat = __webpack_require__(552); +var toRegex = __webpack_require__(553); /** * Return a range of numbers or letters. @@ -62912,7 +63063,7 @@ module.exports = fillRange; /***/ }), -/* 547 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62925,7 +63076,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(548); +var typeOf = __webpack_require__(549); module.exports = function isNumber(num) { var type = typeOf(num); @@ -62941,10 +63092,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 548 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -63063,13 +63214,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 549 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(550); +var isObject = __webpack_require__(551); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -63103,7 +63254,7 @@ function hasOwn(obj, key) { /***/ }), -/* 550 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63123,7 +63274,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 551 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63200,7 +63351,7 @@ function repeat(str, num) { /***/ }), -/* 552 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63213,8 +63364,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(551); -var isNumber = __webpack_require__(547); +var repeat = __webpack_require__(552); +var isNumber = __webpack_require__(548); var cache = {}; function toRegexRange(min, max, options) { @@ -63501,7 +63652,7 @@ module.exports = toRegexRange; /***/ }), -/* 553 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63526,14 +63677,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 554 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(555); -var utils = __webpack_require__(538); +var Node = __webpack_require__(556); +var utils = __webpack_require__(539); /** * Braces parsers @@ -63893,15 +64044,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 555 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(543); -var define = __webpack_require__(556); -var utils = __webpack_require__(563); +var isObject = __webpack_require__(544); +var define = __webpack_require__(557); +var utils = __webpack_require__(564); var ownNames; /** @@ -64392,7 +64543,7 @@ exports = module.exports = Node; /***/ }), -/* 556 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64405,7 +64556,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(557); +var isDescriptor = __webpack_require__(558); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -64430,7 +64581,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 557 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64443,9 +64594,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(558); -var isAccessor = __webpack_require__(559); -var isData = __webpack_require__(561); +var typeOf = __webpack_require__(559); +var isAccessor = __webpack_require__(560); +var isData = __webpack_require__(562); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -64459,7 +64610,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 558 */ +/* 559 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64594,7 +64745,7 @@ function isBuffer(val) { /***/ }), -/* 559 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64607,7 +64758,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(560); +var typeOf = __webpack_require__(561); // accessor descriptor properties var accessor = { @@ -64670,7 +64821,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 560 */ +/* 561 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64805,7 +64956,7 @@ function isBuffer(val) { /***/ }), -/* 561 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64818,7 +64969,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(563); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -64861,7 +65012,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 562 */ +/* 563 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -64996,13 +65147,13 @@ function isBuffer(val) { /***/ }), -/* 563 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(548); +var typeOf = __webpack_require__(549); var utils = module.exports; /** @@ -66022,17 +66173,17 @@ function assert(val, message) { /***/ }), -/* 564 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(531); -var Snapdragon = __webpack_require__(565); -var compilers = __webpack_require__(537); -var parsers = __webpack_require__(554); -var utils = __webpack_require__(538); +var extend = __webpack_require__(532); +var Snapdragon = __webpack_require__(566); +var compilers = __webpack_require__(538); +var parsers = __webpack_require__(555); +var utils = __webpack_require__(539); /** * Customize Snapdragon parser and renderer @@ -66133,17 +66284,17 @@ module.exports = Braces; /***/ }), -/* 565 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(566); -var define = __webpack_require__(592); -var Compiler = __webpack_require__(602); -var Parser = __webpack_require__(631); -var utils = __webpack_require__(611); +var Base = __webpack_require__(567); +var define = __webpack_require__(593); +var Compiler = __webpack_require__(603); +var Parser = __webpack_require__(632); +var utils = __webpack_require__(612); var regexCache = {}; var cache = {}; @@ -66314,20 +66465,20 @@ module.exports.Parser = Parser; /***/ }), -/* 566 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var define = __webpack_require__(567); -var CacheBase = __webpack_require__(568); -var Emitter = __webpack_require__(569); -var isObject = __webpack_require__(543); -var merge = __webpack_require__(586); -var pascal = __webpack_require__(589); -var cu = __webpack_require__(590); +var define = __webpack_require__(568); +var CacheBase = __webpack_require__(569); +var Emitter = __webpack_require__(570); +var isObject = __webpack_require__(544); +var merge = __webpack_require__(587); +var pascal = __webpack_require__(590); +var cu = __webpack_require__(591); /** * Optionally define a custom `cache` namespace to use. @@ -66756,7 +66907,7 @@ module.exports.namespace = namespace; /***/ }), -/* 567 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66769,7 +66920,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(557); +var isDescriptor = __webpack_require__(558); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66794,21 +66945,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 568 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(543); -var Emitter = __webpack_require__(569); -var visit = __webpack_require__(570); -var toPath = __webpack_require__(573); -var union = __webpack_require__(574); -var del = __webpack_require__(578); -var get = __webpack_require__(576); -var has = __webpack_require__(583); -var set = __webpack_require__(577); +var isObject = __webpack_require__(544); +var Emitter = __webpack_require__(570); +var visit = __webpack_require__(571); +var toPath = __webpack_require__(574); +var union = __webpack_require__(575); +var del = __webpack_require__(579); +var get = __webpack_require__(577); +var has = __webpack_require__(584); +var set = __webpack_require__(578); /** * Create a `Cache` constructor that when instantiated will @@ -67062,7 +67213,7 @@ module.exports.namespace = namespace; /***/ }), -/* 569 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { @@ -67231,7 +67382,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 570 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67244,8 +67395,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(571); -var mapVisit = __webpack_require__(572); +var visit = __webpack_require__(572); +var mapVisit = __webpack_require__(573); module.exports = function(collection, method, val) { var result; @@ -67268,7 +67419,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 571 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67281,7 +67432,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(543); +var isObject = __webpack_require__(544); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -67308,14 +67459,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 572 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var visit = __webpack_require__(571); +var visit = __webpack_require__(572); /** * Map `visit` over an array of objects. @@ -67352,7 +67503,7 @@ function isObject(val) { /***/ }), -/* 573 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67365,7 +67516,7 @@ function isObject(val) { -var typeOf = __webpack_require__(548); +var typeOf = __webpack_require__(549); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -67392,16 +67543,16 @@ function filter(arr) { /***/ }), -/* 574 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(535); -var union = __webpack_require__(575); -var get = __webpack_require__(576); -var set = __webpack_require__(577); +var isObject = __webpack_require__(536); +var union = __webpack_require__(576); +var get = __webpack_require__(577); +var set = __webpack_require__(578); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -67429,7 +67580,7 @@ function arrayify(val) { /***/ }), -/* 575 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67465,7 +67616,7 @@ module.exports = function union(init) { /***/ }), -/* 576 */ +/* 577 */ /***/ (function(module, exports) { /*! @@ -67521,7 +67672,7 @@ function toString(val) { /***/ }), -/* 577 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67534,10 +67685,10 @@ function toString(val) { -var split = __webpack_require__(539); -var extend = __webpack_require__(534); -var isPlainObject = __webpack_require__(542); -var isObject = __webpack_require__(535); +var split = __webpack_require__(540); +var extend = __webpack_require__(535); +var isPlainObject = __webpack_require__(543); +var isObject = __webpack_require__(536); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67583,7 +67734,7 @@ function isValidKey(key) { /***/ }), -/* 578 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67596,8 +67747,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(543); -var has = __webpack_require__(579); +var isObject = __webpack_require__(544); +var has = __webpack_require__(580); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -67622,7 +67773,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 579 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67635,9 +67786,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(580); -var hasValues = __webpack_require__(582); -var get = __webpack_require__(576); +var isObject = __webpack_require__(581); +var hasValues = __webpack_require__(583); +var get = __webpack_require__(577); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -67648,7 +67799,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 580 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67661,7 +67812,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(581); +var isArray = __webpack_require__(582); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -67669,7 +67820,7 @@ module.exports = function isObject(val) { /***/ }), -/* 581 */ +/* 582 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -67680,7 +67831,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 582 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67723,7 +67874,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 583 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67736,9 +67887,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(543); -var hasValues = __webpack_require__(584); -var get = __webpack_require__(576); +var isObject = __webpack_require__(544); +var hasValues = __webpack_require__(585); +var get = __webpack_require__(577); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -67746,7 +67897,7 @@ module.exports = function(val, prop) { /***/ }), -/* 584 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67759,8 +67910,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(585); -var isNumber = __webpack_require__(547); +var typeOf = __webpack_require__(586); +var isNumber = __webpack_require__(548); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -67813,10 +67964,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 585 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -67938,14 +68089,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 586 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(587); -var forIn = __webpack_require__(588); +var isExtendable = __webpack_require__(588); +var forIn = __webpack_require__(589); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -68009,7 +68160,7 @@ module.exports = mixinDeep; /***/ }), -/* 587 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68022,7 +68173,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(542); +var isPlainObject = __webpack_require__(543); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68030,7 +68181,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 588 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68053,7 +68204,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 589 */ +/* 590 */ /***/ (function(module, exports) { /*! @@ -68080,14 +68231,14 @@ module.exports = pascalcase; /***/ }), -/* 590 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(111); -var utils = __webpack_require__(591); +var utils = __webpack_require__(592); /** * Expose class utils @@ -68452,7 +68603,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 591 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68466,10 +68617,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(575); -utils.define = __webpack_require__(592); -utils.isObj = __webpack_require__(543); -utils.staticExtend = __webpack_require__(599); +utils.union = __webpack_require__(576); +utils.define = __webpack_require__(593); +utils.isObj = __webpack_require__(544); +utils.staticExtend = __webpack_require__(600); /** @@ -68480,7 +68631,7 @@ module.exports = utils; /***/ }), -/* 592 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68493,7 +68644,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(593); +var isDescriptor = __webpack_require__(594); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68518,7 +68669,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 593 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68531,9 +68682,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(594); -var isAccessor = __webpack_require__(595); -var isData = __webpack_require__(597); +var typeOf = __webpack_require__(595); +var isAccessor = __webpack_require__(596); +var isData = __webpack_require__(598); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -68547,7 +68698,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 594 */ +/* 595 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -68700,7 +68851,7 @@ function isBuffer(val) { /***/ }), -/* 595 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68713,7 +68864,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(596); +var typeOf = __webpack_require__(597); // accessor descriptor properties var accessor = { @@ -68776,10 +68927,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 596 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -68898,7 +69049,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 597 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68911,7 +69062,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(598); +var typeOf = __webpack_require__(599); // data descriptor properties var data = { @@ -68960,10 +69111,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 598 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(528); +var isBuffer = __webpack_require__(529); var toString = Object.prototype.toString; /** @@ -69082,7 +69233,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 599 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69095,8 +69246,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(600); -var define = __webpack_require__(592); +var copy = __webpack_require__(601); +var define = __webpack_require__(593); var util = __webpack_require__(111); /** @@ -69179,15 +69330,15 @@ module.exports = extend; /***/ }), -/* 600 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(548); -var copyDescriptor = __webpack_require__(601); -var define = __webpack_require__(592); +var typeOf = __webpack_require__(549); +var copyDescriptor = __webpack_require__(602); +var define = __webpack_require__(593); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -69360,7 +69511,7 @@ module.exports.has = has; /***/ }), -/* 601 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69448,16 +69599,16 @@ function isObject(val) { /***/ }), -/* 602 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(603); -var define = __webpack_require__(592); -var debug = __webpack_require__(605)('snapdragon:compiler'); -var utils = __webpack_require__(611); +var use = __webpack_require__(604); +var define = __webpack_require__(593); +var debug = __webpack_require__(606)('snapdragon:compiler'); +var utils = __webpack_require__(612); /** * Create a new `Compiler` with the given `options`. @@ -69611,7 +69762,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(630); + var sourcemaps = __webpack_require__(631); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -69632,7 +69783,7 @@ module.exports = Compiler; /***/ }), -/* 603 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69645,7 +69796,7 @@ module.exports = Compiler; -var utils = __webpack_require__(604); +var utils = __webpack_require__(605); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -69760,7 +69911,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 604 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69774,8 +69925,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(592); -utils.isObject = __webpack_require__(543); +utils.define = __webpack_require__(593); +utils.isObject = __webpack_require__(544); utils.isString = function(val) { @@ -69790,7 +69941,7 @@ module.exports = utils; /***/ }), -/* 605 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69799,14 +69950,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(606); + module.exports = __webpack_require__(607); } else { - module.exports = __webpack_require__(609); + module.exports = __webpack_require__(610); } /***/ }), -/* 606 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69815,7 +69966,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(607); +exports = module.exports = __webpack_require__(608); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -69997,7 +70148,7 @@ function localstorage() { /***/ }), -/* 607 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { @@ -70013,7 +70164,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(608); +exports.humanize = __webpack_require__(609); /** * The currently active debug mode names, and names to skip. @@ -70205,7 +70356,7 @@ function coerce(val) { /***/ }), -/* 608 */ +/* 609 */ /***/ (function(module, exports) { /** @@ -70363,14 +70514,14 @@ function plural(ms, n, name) { /***/ }), -/* 609 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(277); +var tty = __webpack_require__(278); var util = __webpack_require__(111); /** @@ -70379,7 +70530,7 @@ var util = __webpack_require__(111); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(607); +exports = module.exports = __webpack_require__(608); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -70558,7 +70709,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(610); + var net = __webpack_require__(611); stream = new net.Socket({ fd: fd, readable: false, @@ -70617,13 +70768,13 @@ exports.enable(load()); /***/ }), -/* 610 */ +/* 611 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 611 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70633,9 +70784,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(534); -exports.SourceMap = __webpack_require__(612); -exports.sourceMapResolve = __webpack_require__(623); +exports.extend = __webpack_require__(535); +exports.SourceMap = __webpack_require__(613); +exports.sourceMapResolve = __webpack_require__(624); /** * Convert backslash in the given string to forward slashes @@ -70678,7 +70829,7 @@ exports.last = function(arr, n) { /***/ }), -/* 612 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -70686,13 +70837,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__(613).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(619).SourceMapConsumer; -exports.SourceNode = __webpack_require__(622).SourceNode; +exports.SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(620).SourceMapConsumer; +exports.SourceNode = __webpack_require__(623).SourceNode; /***/ }), -/* 613 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70702,10 +70853,10 @@ exports.SourceNode = __webpack_require__(622).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(614); -var util = __webpack_require__(616); -var ArraySet = __webpack_require__(617).ArraySet; -var MappingList = __webpack_require__(618).MappingList; +var base64VLQ = __webpack_require__(615); +var util = __webpack_require__(617); +var ArraySet = __webpack_require__(618).ArraySet; +var MappingList = __webpack_require__(619).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -71114,7 +71265,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 614 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71154,7 +71305,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(615); +var base64 = __webpack_require__(616); // 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, @@ -71260,7 +71411,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 615 */ +/* 616 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71333,7 +71484,7 @@ exports.decode = function (charCode) { /***/ }), -/* 616 */ +/* 617 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71756,7 +71907,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 617 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71766,7 +71917,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(616); +var util = __webpack_require__(617); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -71883,7 +72034,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 618 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71893,7 +72044,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(616); +var util = __webpack_require__(617); /** * Determine whether mappingB is after mappingA with respect to generated @@ -71968,7 +72119,7 @@ exports.MappingList = MappingList; /***/ }), -/* 619 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71978,11 +72129,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(616); -var binarySearch = __webpack_require__(620); -var ArraySet = __webpack_require__(617).ArraySet; -var base64VLQ = __webpack_require__(614); -var quickSort = __webpack_require__(621).quickSort; +var util = __webpack_require__(617); +var binarySearch = __webpack_require__(621); +var ArraySet = __webpack_require__(618).ArraySet; +var base64VLQ = __webpack_require__(615); +var quickSort = __webpack_require__(622).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -73056,7 +73207,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 620 */ +/* 621 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73173,7 +73324,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 621 */ +/* 622 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73293,7 +73444,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 622 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73303,8 +73454,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(613).SourceMapGenerator; -var util = __webpack_require__(616); +var SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; +var util = __webpack_require__(617); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -73712,17 +73863,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 623 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(624) -var resolveUrl = __webpack_require__(625) -var decodeUriComponent = __webpack_require__(626) -var urix = __webpack_require__(628) -var atob = __webpack_require__(629) +var sourceMappingURL = __webpack_require__(625) +var resolveUrl = __webpack_require__(626) +var decodeUriComponent = __webpack_require__(627) +var urix = __webpack_require__(629) +var atob = __webpack_require__(630) @@ -74020,7 +74171,7 @@ module.exports = { /***/ }), -/* 624 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -74083,7 +74234,7 @@ void (function(root, factory) { /***/ }), -/* 625 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74101,13 +74252,13 @@ module.exports = resolveUrl /***/ }), -/* 626 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(627) +var decodeUriComponent = __webpack_require__(628) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -74118,7 +74269,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 627 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74219,7 +74370,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 628 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74242,7 +74393,7 @@ module.exports = urix /***/ }), -/* 629 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74256,7 +74407,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 630 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74264,8 +74415,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(132); var path = __webpack_require__(4); -var define = __webpack_require__(592); -var utils = __webpack_require__(611); +var define = __webpack_require__(593); +var utils = __webpack_require__(612); /** * Expose `mixin()`. @@ -74408,19 +74559,19 @@ exports.comment = function(node) { /***/ }), -/* 631 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(603); +var use = __webpack_require__(604); var util = __webpack_require__(111); -var Cache = __webpack_require__(632); -var define = __webpack_require__(592); -var debug = __webpack_require__(605)('snapdragon:parser'); -var Position = __webpack_require__(633); -var utils = __webpack_require__(611); +var Cache = __webpack_require__(633); +var define = __webpack_require__(593); +var debug = __webpack_require__(606)('snapdragon:parser'); +var Position = __webpack_require__(634); +var utils = __webpack_require__(612); /** * Create a new `Parser` with the given `input` and `options`. @@ -74948,7 +75099,7 @@ module.exports = Parser; /***/ }), -/* 632 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75055,13 +75206,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 633 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(592); +var define = __webpack_require__(593); /** * Store position for a node @@ -75076,16 +75227,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 634 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(635); -var define = __webpack_require__(641); -var extend = __webpack_require__(642); -var not = __webpack_require__(644); +var safe = __webpack_require__(636); +var define = __webpack_require__(642); +var extend = __webpack_require__(643); +var not = __webpack_require__(645); var MAX_LENGTH = 1024 * 64; /** @@ -75238,10 +75389,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 635 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(636); +var parse = __webpack_require__(637); var types = parse.types; module.exports = function (re, opts) { @@ -75287,13 +75438,13 @@ function isRegExp (x) { /***/ }), -/* 636 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(637); -var types = __webpack_require__(638); -var sets = __webpack_require__(639); -var positions = __webpack_require__(640); +var util = __webpack_require__(638); +var types = __webpack_require__(639); +var sets = __webpack_require__(640); +var positions = __webpack_require__(641); module.exports = function(regexpStr) { @@ -75575,11 +75726,11 @@ module.exports.types = types; /***/ }), -/* 637 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(638); -var sets = __webpack_require__(639); +var types = __webpack_require__(639); +var sets = __webpack_require__(640); // All of these are private and only used by randexp. @@ -75692,7 +75843,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 638 */ +/* 639 */ /***/ (function(module, exports) { module.exports = { @@ -75708,10 +75859,10 @@ module.exports = { /***/ }), -/* 639 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(638); +var types = __webpack_require__(639); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -75796,10 +75947,10 @@ exports.anyChar = function() { /***/ }), -/* 640 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(638); +var types = __webpack_require__(639); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -75819,7 +75970,7 @@ exports.end = function() { /***/ }), -/* 641 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75832,8 +75983,8 @@ exports.end = function() { -var isobject = __webpack_require__(543); -var isDescriptor = __webpack_require__(557); +var isobject = __webpack_require__(544); +var isDescriptor = __webpack_require__(558); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -75864,14 +76015,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 642 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(643); -var assignSymbols = __webpack_require__(544); +var isExtendable = __webpack_require__(644); +var assignSymbols = __webpack_require__(545); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -75931,7 +76082,7 @@ function isEnum(obj, key) { /***/ }), -/* 643 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75944,7 +76095,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(542); +var isPlainObject = __webpack_require__(543); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -75952,14 +76103,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 644 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(642); -var safe = __webpack_require__(635); +var extend = __webpack_require__(643); +var safe = __webpack_require__(636); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -76031,14 +76182,14 @@ module.exports = toRegex; /***/ }), -/* 645 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(646); -var extglob = __webpack_require__(662); +var nanomatch = __webpack_require__(647); +var extglob = __webpack_require__(663); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -76115,7 +76266,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 646 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76126,17 +76277,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(111); -var toRegex = __webpack_require__(647); -var extend = __webpack_require__(648); +var toRegex = __webpack_require__(648); +var extend = __webpack_require__(649); /** * Local dependencies */ -var compilers = __webpack_require__(650); -var parsers = __webpack_require__(651); -var cache = __webpack_require__(654); -var utils = __webpack_require__(656); +var compilers = __webpack_require__(651); +var parsers = __webpack_require__(652); +var cache = __webpack_require__(655); +var utils = __webpack_require__(657); var MAX_LENGTH = 1024 * 64; /** @@ -76960,15 +77111,15 @@ module.exports = nanomatch; /***/ }), -/* 647 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(592); -var extend = __webpack_require__(534); -var not = __webpack_require__(533); +var define = __webpack_require__(593); +var extend = __webpack_require__(535); +var not = __webpack_require__(534); var MAX_LENGTH = 1024 * 64; /** @@ -77115,14 +77266,14 @@ module.exports.makeRe = makeRe; /***/ }), -/* 648 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(649); -var assignSymbols = __webpack_require__(544); +var isExtendable = __webpack_require__(650); +var assignSymbols = __webpack_require__(545); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -77182,7 +77333,7 @@ function isEnum(obj, key) { /***/ }), -/* 649 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77195,7 +77346,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(542); +var isPlainObject = __webpack_require__(543); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -77203,7 +77354,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 650 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77549,15 +77700,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 651 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(533); -var toRegex = __webpack_require__(647); -var isOdd = __webpack_require__(652); +var regexNot = __webpack_require__(534); +var toRegex = __webpack_require__(648); +var isOdd = __webpack_require__(653); /** * Characters to use in negation regex (we want to "not" match @@ -77943,7 +78094,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 652 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77956,7 +78107,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(653); +var isNumber = __webpack_require__(654); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -77970,7 +78121,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 653 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77998,14 +78149,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 654 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(655))(); +module.exports = new (__webpack_require__(656))(); /***/ }), -/* 655 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78018,7 +78169,7 @@ module.exports = new (__webpack_require__(655))(); -var MapCache = __webpack_require__(632); +var MapCache = __webpack_require__(633); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -78140,7 +78291,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 656 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78153,14 +78304,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(657)(); -var Snapdragon = __webpack_require__(565); -utils.define = __webpack_require__(658); -utils.diff = __webpack_require__(659); -utils.extend = __webpack_require__(648); -utils.pick = __webpack_require__(660); -utils.typeOf = __webpack_require__(661); -utils.unique = __webpack_require__(536); +var isWindows = __webpack_require__(658)(); +var Snapdragon = __webpack_require__(566); +utils.define = __webpack_require__(659); +utils.diff = __webpack_require__(660); +utils.extend = __webpack_require__(649); +utils.pick = __webpack_require__(661); +utils.typeOf = __webpack_require__(662); +utils.unique = __webpack_require__(537); /** * Returns true if the given value is effectively an empty string @@ -78526,7 +78677,7 @@ utils.unixify = function(options) { /***/ }), -/* 657 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -78554,7 +78705,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 658 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78567,8 +78718,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(543); -var isDescriptor = __webpack_require__(557); +var isobject = __webpack_require__(544); +var isDescriptor = __webpack_require__(558); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -78599,7 +78750,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 659 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78653,7 +78804,7 @@ function diffArray(one, two) { /***/ }), -/* 660 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78666,7 +78817,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(543); +var isObject = __webpack_require__(544); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -78695,7 +78846,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 661 */ +/* 662 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -78830,7 +78981,7 @@ function isBuffer(val) { /***/ }), -/* 662 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78840,18 +78991,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(534); -var unique = __webpack_require__(536); -var toRegex = __webpack_require__(647); +var extend = __webpack_require__(535); +var unique = __webpack_require__(537); +var toRegex = __webpack_require__(648); /** * Local dependencies */ -var compilers = __webpack_require__(663); -var parsers = __webpack_require__(674); -var Extglob = __webpack_require__(677); -var utils = __webpack_require__(676); +var compilers = __webpack_require__(664); +var parsers = __webpack_require__(675); +var Extglob = __webpack_require__(678); +var utils = __webpack_require__(677); var MAX_LENGTH = 1024 * 64; /** @@ -79168,13 +79319,13 @@ module.exports = extglob; /***/ }), -/* 663 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(664); +var brackets = __webpack_require__(665); /** * Extglob compilers @@ -79344,7 +79495,7 @@ module.exports = function(extglob) { /***/ }), -/* 664 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79354,17 +79505,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(665); -var parsers = __webpack_require__(667); +var compilers = __webpack_require__(666); +var parsers = __webpack_require__(668); /** * Module dependencies */ -var debug = __webpack_require__(669)('expand-brackets'); -var extend = __webpack_require__(534); -var Snapdragon = __webpack_require__(565); -var toRegex = __webpack_require__(647); +var debug = __webpack_require__(670)('expand-brackets'); +var extend = __webpack_require__(535); +var Snapdragon = __webpack_require__(566); +var toRegex = __webpack_require__(648); /** * Parses the given POSIX character class `pattern` and returns a @@ -79562,13 +79713,13 @@ module.exports = brackets; /***/ }), -/* 665 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(666); +var posix = __webpack_require__(667); module.exports = function(brackets) { brackets.compiler @@ -79656,7 +79807,7 @@ module.exports = function(brackets) { /***/ }), -/* 666 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79685,14 +79836,14 @@ module.exports = { /***/ }), -/* 667 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(668); -var define = __webpack_require__(592); +var utils = __webpack_require__(669); +var define = __webpack_require__(593); /** * Text regex @@ -79911,14 +80062,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 668 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(647); -var regexNot = __webpack_require__(533); +var toRegex = __webpack_require__(648); +var regexNot = __webpack_require__(534); var cached; /** @@ -79952,7 +80103,7 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 669 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -79961,14 +80112,14 @@ exports.createRegex = function(pattern, include) { */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(670); + module.exports = __webpack_require__(671); } else { - module.exports = __webpack_require__(673); + module.exports = __webpack_require__(674); } /***/ }), -/* 670 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -79977,7 +80128,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(671); +exports = module.exports = __webpack_require__(672); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -80159,7 +80310,7 @@ function localstorage() { /***/ }), -/* 671 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { @@ -80175,7 +80326,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(672); +exports.humanize = __webpack_require__(673); /** * The currently active debug mode names, and names to skip. @@ -80367,7 +80518,7 @@ function coerce(val) { /***/ }), -/* 672 */ +/* 673 */ /***/ (function(module, exports) { /** @@ -80525,14 +80676,14 @@ function plural(ms, n, name) { /***/ }), -/* 673 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(277); +var tty = __webpack_require__(278); var util = __webpack_require__(111); /** @@ -80541,7 +80692,7 @@ var util = __webpack_require__(111); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(671); +exports = module.exports = __webpack_require__(672); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -80720,7 +80871,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(610); + var net = __webpack_require__(611); stream = new net.Socket({ fd: fd, readable: false, @@ -80779,15 +80930,15 @@ exports.enable(load()); /***/ }), -/* 674 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(664); -var define = __webpack_require__(675); -var utils = __webpack_require__(676); +var brackets = __webpack_require__(665); +var define = __webpack_require__(676); +var utils = __webpack_require__(677); /** * Characters to use in text regex (we want to "not" match @@ -80942,7 +81093,7 @@ module.exports = parsers; /***/ }), -/* 675 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80955,7 +81106,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(557); +var isDescriptor = __webpack_require__(558); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -80980,14 +81131,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 676 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(533); -var Cache = __webpack_require__(655); +var regex = __webpack_require__(534); +var Cache = __webpack_require__(656); /** * Utils @@ -81056,7 +81207,7 @@ utils.createRegex = function(str) { /***/ }), -/* 677 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81066,16 +81217,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(565); -var define = __webpack_require__(675); -var extend = __webpack_require__(534); +var Snapdragon = __webpack_require__(566); +var define = __webpack_require__(676); +var extend = __webpack_require__(535); /** * Local dependencies */ -var compilers = __webpack_require__(663); -var parsers = __webpack_require__(674); +var compilers = __webpack_require__(664); +var parsers = __webpack_require__(675); /** * Customize Snapdragon parser and renderer @@ -81141,16 +81292,16 @@ module.exports = Extglob; /***/ }), -/* 678 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(662); -var nanomatch = __webpack_require__(646); -var regexNot = __webpack_require__(533); -var toRegex = __webpack_require__(634); +var extglob = __webpack_require__(663); +var nanomatch = __webpack_require__(647); +var regexNot = __webpack_require__(534); +var toRegex = __webpack_require__(635); var not; /** @@ -81231,14 +81382,14 @@ function textRegex(pattern) { /***/ }), -/* 679 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(655))(); +module.exports = new (__webpack_require__(656))(); /***/ }), -/* 680 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81251,13 +81402,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(565); -utils.define = __webpack_require__(641); -utils.diff = __webpack_require__(659); -utils.extend = __webpack_require__(642); -utils.pick = __webpack_require__(660); -utils.typeOf = __webpack_require__(681); -utils.unique = __webpack_require__(536); +var Snapdragon = __webpack_require__(566); +utils.define = __webpack_require__(642); +utils.diff = __webpack_require__(660); +utils.extend = __webpack_require__(643); +utils.pick = __webpack_require__(661); +utils.typeOf = __webpack_require__(682); +utils.unique = __webpack_require__(537); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -81554,7 +81705,7 @@ utils.unixify = function(options) { /***/ }), -/* 681 */ +/* 682 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -81689,7 +81840,7 @@ function isBuffer(val) { /***/ }), -/* 682 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81708,9 +81859,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(683); -var reader_1 = __webpack_require__(696); -var fs_stream_1 = __webpack_require__(700); +var readdir = __webpack_require__(684); +var reader_1 = __webpack_require__(697); +var fs_stream_1 = __webpack_require__(701); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81771,15 +81922,15 @@ exports.default = ReaderAsync; /***/ }), -/* 683 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(684); -const readdirAsync = __webpack_require__(692); -const readdirStream = __webpack_require__(695); +const readdirSync = __webpack_require__(685); +const readdirAsync = __webpack_require__(693); +const readdirStream = __webpack_require__(696); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81863,7 +82014,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 684 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81871,11 +82022,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(685); +const DirectoryReader = __webpack_require__(686); let syncFacade = { - fs: __webpack_require__(690), - forEach: __webpack_require__(691), + fs: __webpack_require__(691), + forEach: __webpack_require__(692), sync: true }; @@ -81904,7 +82055,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 685 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81913,9 +82064,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(136).Readable; const EventEmitter = __webpack_require__(154).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(686); -const stat = __webpack_require__(688); -const call = __webpack_require__(689); +const normalizeOptions = __webpack_require__(687); +const stat = __webpack_require__(689); +const call = __webpack_require__(690); /** * Asynchronously reads the contents of a directory and streams the results @@ -82291,14 +82442,14 @@ module.exports = DirectoryReader; /***/ }), -/* 686 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(687); +const globToRegExp = __webpack_require__(688); module.exports = normalizeOptions; @@ -82475,7 +82626,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 687 */ +/* 688 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82612,13 +82763,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 688 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(689); +const call = __webpack_require__(690); module.exports = stat; @@ -82693,7 +82844,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 689 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82754,14 +82905,14 @@ function callOnce (fn) { /***/ }), -/* 690 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); -const call = __webpack_require__(689); +const call = __webpack_require__(690); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82825,7 +82976,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 691 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82854,7 +83005,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 692 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82862,12 +83013,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(693); -const DirectoryReader = __webpack_require__(685); +const maybe = __webpack_require__(694); +const DirectoryReader = __webpack_require__(686); let asyncFacade = { fs: __webpack_require__(132), - forEach: __webpack_require__(694), + forEach: __webpack_require__(695), async: true }; @@ -82909,7 +83060,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 693 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82936,7 +83087,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 694 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82972,7 +83123,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 695 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82980,11 +83131,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(685); +const DirectoryReader = __webpack_require__(686); let streamFacade = { fs: __webpack_require__(132), - forEach: __webpack_require__(694), + forEach: __webpack_require__(695), async: true }; @@ -83004,16 +83155,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 696 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(697); -var entry_1 = __webpack_require__(699); -var pathUtil = __webpack_require__(698); +var deep_1 = __webpack_require__(698); +var entry_1 = __webpack_require__(700); +var pathUtil = __webpack_require__(699); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -83079,14 +83230,14 @@ exports.default = Reader; /***/ }), -/* 697 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(698); -var patternUtils = __webpack_require__(515); +var pathUtils = __webpack_require__(699); +var patternUtils = __webpack_require__(516); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -83169,7 +83320,7 @@ exports.default = DeepFilter; /***/ }), -/* 698 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83200,14 +83351,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 699 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(698); -var patternUtils = __webpack_require__(515); +var pathUtils = __webpack_require__(699); +var patternUtils = __webpack_require__(516); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -83292,7 +83443,7 @@ exports.default = EntryFilter; /***/ }), -/* 700 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83312,8 +83463,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(136); -var fsStat = __webpack_require__(701); -var fs_1 = __webpack_require__(705); +var fsStat = __webpack_require__(702); +var fs_1 = __webpack_require__(706); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -83363,14 +83514,14 @@ exports.default = FileSystemStream; /***/ }), -/* 701 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(702); -const statProvider = __webpack_require__(704); +const optionsManager = __webpack_require__(703); +const statProvider = __webpack_require__(705); /** * Asynchronous API. */ @@ -83401,13 +83552,13 @@ exports.statSync = statSync; /***/ }), -/* 702 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(703); +const fsAdapter = __webpack_require__(704); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -83420,7 +83571,7 @@ exports.prepare = prepare; /***/ }), -/* 703 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83443,7 +83594,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 704 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83495,7 +83646,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 705 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83526,7 +83677,7 @@ exports.default = FileSystem; /***/ }), -/* 706 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83546,9 +83697,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(136); -var readdir = __webpack_require__(683); -var reader_1 = __webpack_require__(696); -var fs_stream_1 = __webpack_require__(700); +var readdir = __webpack_require__(684); +var reader_1 = __webpack_require__(697); +var fs_stream_1 = __webpack_require__(701); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83616,7 +83767,7 @@ exports.default = ReaderStream; /***/ }), -/* 707 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83635,9 +83786,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(683); -var reader_1 = __webpack_require__(696); -var fs_sync_1 = __webpack_require__(708); +var readdir = __webpack_require__(684); +var reader_1 = __webpack_require__(697); +var fs_sync_1 = __webpack_require__(709); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83697,7 +83848,7 @@ exports.default = ReaderSync; /***/ }), -/* 708 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83716,8 +83867,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(701); -var fs_1 = __webpack_require__(705); +var fsStat = __webpack_require__(702); +var fs_1 = __webpack_require__(706); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83763,7 +83914,7 @@ exports.default = FileSystemSync; /***/ }), -/* 709 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83779,13 +83930,13 @@ exports.flatten = flatten; /***/ }), -/* 710 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(283); +var merge2 = __webpack_require__(284); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -83800,13 +83951,13 @@ exports.merge = merge; /***/ }), -/* 711 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(712); +const pathType = __webpack_require__(713); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83872,13 +84023,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 712 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); -const pify = __webpack_require__(713); +const pify = __webpack_require__(714); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83921,7 +84072,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 713 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84012,17 +84163,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 714 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(132); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(511); -const gitIgnore = __webpack_require__(715); -const pify = __webpack_require__(716); -const slash = __webpack_require__(717); +const fastGlob = __webpack_require__(512); +const gitIgnore = __webpack_require__(716); +const pify = __webpack_require__(717); +const slash = __webpack_require__(718); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -84120,7 +84271,7 @@ module.exports.sync = options => { /***/ }), -/* 715 */ +/* 716 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -84589,7 +84740,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 716 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84664,7 +84815,7 @@ module.exports = (input, options) => { /***/ }), -/* 717 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84682,7 +84833,7 @@ module.exports = input => { /***/ }), -/* 718 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84692,7 +84843,7 @@ module.exports = input => { * Released under the MIT License. */ -var isExtglob = __webpack_require__(299); +var isExtglob = __webpack_require__(300); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -84736,17 +84887,17 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 719 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(132); -const pEvent = __webpack_require__(720); -const CpFileError = __webpack_require__(723); -const fs = __webpack_require__(727); -const ProgressEmitter = __webpack_require__(730); +const pEvent = __webpack_require__(721); +const CpFileError = __webpack_require__(724); +const fs = __webpack_require__(728); +const ProgressEmitter = __webpack_require__(731); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84860,12 +85011,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 720 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(721); +const pTimeout = __webpack_require__(722); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -85156,12 +85307,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 721 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(722); +const pFinally = __webpack_require__(723); class TimeoutError extends Error { constructor(message) { @@ -85207,7 +85358,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 722 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85229,12 +85380,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 723 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(724); +const NestedError = __webpack_require__(725); class CpFileError extends NestedError { constructor(message, nested) { @@ -85248,10 +85399,10 @@ module.exports = CpFileError; /***/ }), -/* 724 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(725); +var inherits = __webpack_require__(726); var NestedError = function (message, nested) { this.nested = nested; @@ -85302,7 +85453,7 @@ module.exports = NestedError; /***/ }), -/* 725 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -85310,12 +85461,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(726); + module.exports = __webpack_require__(727); } /***/ }), -/* 726 */ +/* 727 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -85344,16 +85495,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 727 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(111); const fs = __webpack_require__(131); -const makeDir = __webpack_require__(728); -const pEvent = __webpack_require__(720); -const CpFileError = __webpack_require__(723); +const makeDir = __webpack_require__(729); +const pEvent = __webpack_require__(721); +const CpFileError = __webpack_require__(724); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -85450,7 +85601,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 728 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85458,7 +85609,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(132); const path = __webpack_require__(4); const {promisify} = __webpack_require__(111); -const semver = __webpack_require__(729); +const semver = __webpack_require__(730); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -85613,7 +85764,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 729 */ +/* 730 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -87215,7 +87366,7 @@ function coerce (version, options) { /***/ }), -/* 730 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87256,7 +87407,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 731 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87302,12 +87453,12 @@ exports.default = module.exports; /***/ }), -/* 732 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(733); +const NestedError = __webpack_require__(734); class CpyError extends NestedError { constructor(message, nested) { @@ -87321,7 +87472,7 @@ module.exports = CpyError; /***/ }), -/* 733 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(111).inherits; @@ -87377,7 +87528,7 @@ module.exports = NestedError; /***/ }), -/* 734 */ +/* 735 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 80ccc5daecc56..f8e50a8247856 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -74,10 +74,10 @@ export const BootstrapCommand: ICommand = { if (valid) { log.debug(`[${project.name}] cache up to date`); + cachedProjectCount += 1; } caches.set(project, { file, valid }); - cachedProjectCount += 1; } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 29ec28175a851..e9aeee87f1a3b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -38,6 +38,14 @@ const urlPartsSchema = () => password: Joi.string(), pathname: Joi.string().regex(/^\//, 'start with a /'), hash: Joi.string().regex(/^\//, 'start with a /'), + ssl: Joi.object() + .keys({ + enabled: Joi.boolean().default(false), + certificate: Joi.string().optional(), + certificateAuthorities: Joi.string().optional(), + key: Joi.string().optional(), + }) + .default(), }) .default(); @@ -122,6 +130,7 @@ export const schema = Joi.object() type: Joi.string().valid('chrome', 'firefox', 'ie', 'msedge').default('chrome'), logPollingMs: Joi.number().default(100), + acceptInsecureCerts: Joi.boolean().default(false), }) .default(), diff --git a/packages/kbn-test/src/kbn/index.js b/packages/kbn-test/src/kbn/index.ts similarity index 100% rename from packages/kbn-test/src/kbn/index.js rename to packages/kbn-test/src/kbn/index.ts diff --git a/packages/kbn-test/src/kbn/kbn_test_config.js b/packages/kbn-test/src/kbn/kbn_test_config.ts similarity index 76% rename from packages/kbn-test/src/kbn/kbn_test_config.js rename to packages/kbn-test/src/kbn/kbn_test_config.ts index c43efabb4b747..909c94098cf5d 100644 --- a/packages/kbn-test/src/kbn/kbn_test_config.js +++ b/packages/kbn-test/src/kbn/kbn_test_config.ts @@ -16,26 +16,34 @@ * specific language governing permissions and limitations * under the License. */ - -import { kibanaTestUser } from './users'; import url from 'url'; +import { kibanaTestUser } from './users'; + +interface UrlParts { + protocol?: string; + hostname?: string; + port?: number; + auth?: string; + username?: string; + password?: string; +} export const kbnTestConfig = new (class KbnTestConfig { getPort() { return this.getUrlParts().port; } - getUrlParts() { + getUrlParts(): UrlParts { // allow setting one complete TEST_KIBANA_URL for ES like https://elastic:changeme@example.com:9200 if (process.env.TEST_KIBANA_URL) { const testKibanaUrl = url.parse(process.env.TEST_KIBANA_URL); return { - protocol: testKibanaUrl.protocol.slice(0, -1), + protocol: testKibanaUrl.protocol?.slice(0, -1), hostname: testKibanaUrl.hostname, - port: parseInt(testKibanaUrl.port, 10), + port: testKibanaUrl.port ? parseInt(testKibanaUrl.port, 10) : undefined, auth: testKibanaUrl.auth, - username: testKibanaUrl.auth.split(':')[0], - password: testKibanaUrl.auth.split(':')[1], + username: testKibanaUrl.auth?.split(':')[0], + password: testKibanaUrl.auth?.split(':')[1], }; } @@ -44,7 +52,7 @@ export const kbnTestConfig = new (class KbnTestConfig { return { protocol: process.env.TEST_KIBANA_PROTOCOL || 'http', hostname: process.env.TEST_KIBANA_HOSTNAME || 'localhost', - port: parseInt(process.env.TEST_KIBANA_PORT, 10) || 5620, + port: process.env.TEST_KIBANA_PORT ? parseInt(process.env.TEST_KIBANA_PORT, 10) : 5620, auth: `${username}:${password}`, username, password, diff --git a/packages/kbn-test/src/kbn/users.js b/packages/kbn-test/src/kbn/users.ts similarity index 100% rename from packages/kbn-test/src/kbn/users.js rename to packages/kbn-test/src/kbn/users.ts diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 9b5d614451ad8..ffe40a8fe1c10 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --dev --watch" }, "dependencies": { - "@elastic/charts": "19.2.0", + "@elastic/charts": "19.5.2", "@elastic/eui": "24.1.0", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", diff --git a/src/core/TESTING.md b/src/core/TESTING.md index bed41ab583496..a62922d9b5d64 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -29,6 +29,14 @@ This document outlines best practices and patterns for testing Kibana Plugins. - [Testing dependencies usages](#testing-dependencies-usages) - [Testing components consuming the dependencies](#testing-components-consuming-the-dependencies) - [Testing optional plugin dependencies](#testing-optional-plugin-dependencies) + - [RXJS testing](#rxjs-testing) + - [Testing RXJS observables with marble](#rxjs-testing-with-marble) + - [Precondition](#preconditions-2) + - [Examples](#example-5) + - [Testing an interval based observable](#testing-an-interval-based-observable) + - [Testing observable completion](#testing-observable-completion) + - [Testing observable errors](#testing-observable-errors) + - [Testing promise based observables](#testing-promise-based-observables) ## Strategy @@ -1087,3 +1095,271 @@ describe('Plugin', () => { }); }); ``` + +## RXJS testing + +### Testing RXJS observables with marble + +Testing observable based APIs can be challenging, specially when asynchronous operators or sources are used, +or when trying to assert against emission's timing. + +Fortunately, RXJS comes with it's own `marble` testing module to greatly facilitate that kind of testing. + +See [the official doc](https://rxjs-dev.firebaseapp.com/guide/testing/marble-testing) for more information about marble testing. + +### Preconditions + +The following examples all assume that the following snippet is included in every test file: + +```typescript +import { TestScheduler } from 'rxjs/testing'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); +``` + +`getTestScheduler` creates a `TestScheduler` that is wired on `jest`'s `expect` statement when comparing an observable's time frame. + +### Examples + +#### Testing an interval based observable + +Here is a very basic example of an interval-based API: + +```typescript +class FooService { + setup() { + return { + getUpdate$: () => { + return interval(100).pipe(map((count) => `update-${count + 1}`)); + }, + }; + } +} +``` + +If we were to be adding a test that asserts the correct behavior of this API without using marble testing, it +would probably be something like: + +```typescript +it('getUpdate$ emits updates every 100ms', async () => { + const service = new FooService(); + const { getUpdate$ } = service.setup(); + expect(await getUpdate$().pipe(take(3), toArray()).toPromise()).toEqual([ + 'update-1', + 'update-2', + 'update-3', + ]); +}); +``` + +Note that if we are able to test the correct value of each emission, we don't have any way to assert that +the interval of 100ms was respected. Even using a subscription based test to try to do so would result +in potential flakiness, as the subscription execution could trigger on the `101ms` time frame for example. + +It also may be important to note: +- as we need to convert the observable to a promise and wait for the result, the test is `async` +- we need to perform observable transformation (`take` + `toArray`) in the test to have an usable value to assert against. + +Marble testing would allow to get rid of these limitations. An equivalent and improved marble test could be: + +```typescript + describe('getUpdate$', () => { + it('emits updates every 100ms', () => { + getTestScheduler().run(({ expectObservable }) => { + const { getUpdate$ } = service.setup(); + expectObservable(getUpdate$(), '301ms !').toBe('100ms a 99ms b 99ms c', { + a: 'update-1', + b: 'update-2', + c: 'update-3', + }); + }); + }); + }); +``` + +Notes: +- the test is now synchronous +- the second parameter of `expectObservable` (`'301ms !'`) is used to perform manual unsubscription to the observable, as + `interval` never ends. +- an emission is considered a time frame, meaning that after the initial `a` emission, we are at the frame `101`, not `100` + which is why we are then only using a `99ms` gap between a->b and b->c. + +#### Testing observable completion + +Let's 'improve' our `getUpdate$` API by allowing the consumer to manually terminate the observable chain using +a new `abort$` option: + +```typescript +class FooService { + setup() { + return { + // note: using an abortion observable is usually an anti-pattern, as unsubscribing from the observable + // is, most of the time, a better solution. This is only used for the example purpose. + getUpdate$: ({ abort$ = EMPTY }: { abort$?: Observable } = {}) => { + return interval(100).pipe( + takeUntil(abort$), + map((count) => `update-${count + 1}`) + ); + }, + }; + } +} +``` + +We would then add a test to assert than this new option usage is respected: + +```typescript +it('getUpdate$ completes when `abort$` emits', () => { + const service = new FooService(); + getTestScheduler().run(({ expectObservable, hot }) => { + const { getUpdate$ } = service.setup(); + const abort$ = hot('149ms a', { a: undefined }); + expectObservable(getUpdate$({ abort$ })).toBe('100ms a 48ms |', { + a: 'update-1', + }); + }); +}); +``` + +Notes: + - the `|` symbol represents the completion of the observable. + - we are here using the `hot` testing utility to create the `abort$` observable to ensure correct emission timing. + +#### Testing observable errors + +Testing errors thrown by the observable is very close to the previous examples and is done using +the third parameter of `expectObservable`. + +Say we have a service in charge of processing data from an observable and returning the results in a new observable: + +```typescript +interface SomeDataType { + id: string; +} + +class BarService { + setup() { + return { + processDataStream: (data$: Observable) => { + return data$.pipe( + map((data) => { + if (data.id === 'invalid') { + throw new Error(`invalid data: '${data.id}'`); + } + return { + ...data, + processed: 'additional-data', + }; + }) + ); + }, + }; + } +} +``` + +We could write a test that asserts the service properly emit processed results until an invalid data is encountered: + +```typescript +it('processDataStream throw an error when processing invalid data', () => { + getTestScheduler().run(({ expectObservable, hot }) => { + const service = new BarService(); + const { processDataStream } = service.setup(); + + const data = hot('--a--b--(c|)', { + a: { id: 'a' }, + b: { id: 'invalid' }, + c: { id: 'c' }, + }); + + expectObservable(processDataStream(data)).toBe( + '--a--#', + { + a: { id: 'a', processed: 'additional-data' }, + }, + `'[Error: invalid data: 'invalid']'` + ); + }); +}); +``` + +Notes: + - the `-` symbol represents one virtual time frame. + - the `#` symbol represents an error. + - when throwing custom `Error` classes, the assertion can be against an error instance, but this doesn't work + with base errors. + +#### Testing promise based observables + +In some cases, the observable we want to test is based on a Promise (like `of(somePromise).pipe(...)`). This can occur +when using promise-based services, such as core's `http`, for instance. + +```typescript +export const callServerAPI = ( + http: HttpStart, + body: Record, + { abort$ }: { abort$: Observable } +): Observable => { + let controller: AbortController | undefined; + if (abort$) { + controller = new AbortController(); + abort$.subscribe(() => { + controller!.abort(); + }); + } + return from( + http.post('/api/endpoint', { + body, + signal: controller?.signal, + }) + ).pipe( + takeUntil(abort$ ?? EMPTY), + map((response) => response.results) + ); +}; +``` + +Testing that kind of promise based observable does not work out of the box with marble testing, as the asynchronous promise resolution +is not handled by the test scheduler's 'sandbox'. + +Fortunately, there are workarounds for this problem. The most common one being to mock the promise-returning API to return +an observable instead for testing, as `of(observable)` also works and returns the input observable. + +Note that when doing so, the test suite must also include tests using a real promise value to ensure correct behavior in real situation. + +```typescript + +// NOTE: test scheduler do not properly work with promises because of their asynchronous nature. +// we are cheating here by having `http.post` return an observable instead of a promise. +// this still allows more finely grained testing about timing, and asserting that the method +// works properly when `post` returns a real promise is handled in other tests of this suite + +it('callServerAPI result observable emits when the response is received', () => { + const http = httpServiceMock.createStartContract(); + getTestScheduler().run(({ expectObservable, hot }) => { + // need to cast the observable as `any` because http.post.mockReturnValue expects a promise, see previous comment + http.post.mockReturnValue(hot('---(a|)', { a: { someData: 'foo' } }) as any); + + const results = callServerAPI(http, { query: 'term' }, {}); + + expectObservable(results).toBe('---(a|)', { + a: { someData: 'foo' }, + }); + }); +}); + +it('completes without returning results if aborted$ emits before the response', () => { + const http = httpServiceMock.createStartContract(); + getTestScheduler().run(({ expectObservable, hot }) => { + // need to cast the observable as `any` because http.post.mockReturnValue expects a promise, see previous comment + http.post.mockReturnValue(hot('---(a|)', { a: { someData: 'foo' } }) as any); + const aborted$ = hot('-(a|)', { a: undefined }); + const results = callServerAPI(http, { query: 'term' }, { aborted$ }); + + expectObservable(results).toBe('-|'); + }); +}); +``` \ No newline at end of file diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index aa52212344f4d..d6172b77d3ca5 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -300,6 +300,10 @@ export class CoreSystem { plugins: mapToObject(plugins.contracts), targetDomElement: rendering.legacyTargetDomElement, }); + + return { + application, + }; } catch (error) { if (this.fatalErrorsSetup) { this.fatalErrorsSetup.add(error); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index aeeb8c3342e46..f2bc90a5b08d4 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -37,6 +37,9 @@ export class DocLinksService { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL, links: { + dashboard: { + drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, + }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, installation: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-installation.html`, @@ -144,6 +147,9 @@ export interface DocLinksSetup { readonly DOC_LINK_VERSION: string; readonly ELASTIC_WEBSITE_URL: string; readonly links: { + readonly dashboard: { + readonly drilldowns: string; + }; readonly filebeat: { readonly base: string; readonly installation: string; diff --git a/src/core/public/kbn_bootstrap.ts b/src/core/public/kbn_bootstrap.ts index caeb95a540de3..0f86061816701 100644 --- a/src/core/public/kbn_bootstrap.ts +++ b/src/core/public/kbn_bootstrap.ts @@ -38,11 +38,17 @@ export function __kbnBootstrap__() { * `apmConfig` would be populated with relavant APM RUM agent * configuration if server is started with `ELASTIC_APM_ACTIVE=true` */ - if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && injectedMetadata.vars.apmConfig != null) { + const apmConfig = injectedMetadata.vars.apmConfig; + const APM_ENABLED = process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && apmConfig != null; + + if (APM_ENABLED) { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-var-requires - const { init } = require('@elastic/apm-rum'); - init(injectedMetadata.vars.apmConfig); + const { init, apm } = require('@elastic/apm-rum'); + if (apmConfig.globalLabels) { + apm.addLabels(apmConfig.globalLabels); + } + init(apmConfig); } i18n @@ -60,6 +66,22 @@ export function __kbnBootstrap__() { setup.fatalErrors.add(i18nError); } - await coreSystem.start(); + const start = await coreSystem.start(); + + if (APM_ENABLED && start) { + /** + * Register listeners for navigation changes and capture them as + * route-change transactions after Kibana app is bootstrapped + */ + start.application.currentAppId$.subscribe((appId) => { + const apmInstance = (window as any).elasticApm; + if (appId && apmInstance && typeof apmInstance.startTransaction === 'function') { + apmInstance.startTransaction(`/app/${appId}`, 'route-change', { + managed: true, + canReuse: true, + }); + } + }); + } }); } diff --git a/src/core/public/plugins/plugin_reader.test.ts b/src/core/public/plugins/plugin_reader.test.ts index d4324f81de8e6..b3bc84f30daac 100644 --- a/src/core/public/plugins/plugin_reader.test.ts +++ b/src/core/public/plugins/plugin_reader.test.ts @@ -17,25 +17,37 @@ * under the License. */ -import { CoreWindow, read, UnknownPluginInitializer } from './plugin_reader'; +import { CoreWindow, read } from './plugin_reader'; + +const coreWindow: CoreWindow & { + __kbnBundles__: { stub(key: string, value: any): void }; +} = window as any; -const coreWindow: CoreWindow = window as any; beforeEach(() => { - coreWindow.__kbnBundles__ = {}; + const stubs = new Map(); + coreWindow.__kbnBundles__ = { + get(key) { + return stubs.get(key); + }, + has(key) { + return stubs.has(key); + }, + stub(key, value) { + stubs.set(key, value); + }, + }; }); it('handles undefined plugin exports', () => { - coreWindow.__kbnBundles__['plugin/foo'] = undefined; - expect(() => { read('foo'); }).toThrowError(`Definition of plugin "foo" not found and may have failed to load.`); }); it('handles plugin exports with a "plugin" export that is not a function', () => { - coreWindow.__kbnBundles__['plugin/foo'] = { + coreWindow.__kbnBundles__.stub('plugin/foo/public', { plugin: 1234, - } as any; + }); expect(() => { read('foo'); @@ -43,11 +55,8 @@ it('handles plugin exports with a "plugin" export that is not a function', () => }); it('returns the plugin initializer when the "plugin" named export is a function', () => { - const plugin: UnknownPluginInitializer = () => { - return undefined as any; - }; - - coreWindow.__kbnBundles__['plugin/foo'] = { plugin }; + const plugin = () => {}; + coreWindow.__kbnBundles__.stub('plugin/foo/public', { plugin }); expect(read('foo')).toBe(plugin); }); diff --git a/src/core/public/plugins/plugin_reader.ts b/src/core/public/plugins/plugin_reader.ts index 1907dfa6a3e99..d80bda7483775 100644 --- a/src/core/public/plugins/plugin_reader.ts +++ b/src/core/public/plugins/plugin_reader.ts @@ -31,7 +31,8 @@ export type UnknownPluginInitializer = PluginInitializer; // (undocumented) - start(): Promise; + start(): Promise<{ + application: InternalApplicationStart; + } | undefined>; // (undocumented) stop(): void; } @@ -605,6 +607,9 @@ export interface DocLinksSetup { readonly ELASTIC_WEBSITE_URL: string; // (undocumented) readonly links: { + readonly dashboard: { + readonly drilldowns: string; + }; readonly filebeat: { readonly base: string; readonly installation: string; @@ -1587,4 +1592,8 @@ export interface UserProvidedValues { } +// Warnings were encountered during analysis: +// +// src/core/public/core_system.ts:216:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts + ``` diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 5c8eca4a33ec5..cb279b2cc4c8f 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -303,7 +303,6 @@ export class SavedObjectsClient { query, }); return request.then((resp) => { - resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, SavedObjectsFindResponsePublic @@ -314,7 +313,10 @@ export class SavedObjectsClient { per_page: 'perPage', page: 'page', }, - resp + { + ...resp, + saved_objects: resp.saved_objects.map((d) => this.createSavedObject(d)), + } ) as SavedObjectsFindResponsePublic; }); }; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 658c24f835020..dccd58c24a7d0 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -217,6 +217,7 @@ export { SavedObjectsErrorHelpers, SavedObjectsExportOptions, SavedObjectsExportResultDetails, + SavedObjectsFindResult, SavedObjectsFindResponse, SavedObjectsImportConflictError, SavedObjectsImportError, diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 27c3ca5a71e16..f2c3a29eca0ac 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -57,6 +57,7 @@ const KNOWN_MANIFEST_FIELDS = (() => { optionalPlugins: true, ui: true, server: true, + extraPublicDirs: true, }; return new Set(Object.keys(manifestFields)); @@ -70,7 +71,11 @@ const KNOWN_MANIFEST_FIELDS = (() => { * @param packageInfo Kibana package info. * @internal */ -export async function parseManifest(pluginPath: string, packageInfo: PackageInfo, log: Logger) { +export async function parseManifest( + pluginPath: string, + packageInfo: PackageInfo, + log: Logger +): Promise { const manifestPath = resolve(pluginPath, MANIFEST_FILE_NAME); let manifestContent; @@ -130,6 +135,19 @@ export async function parseManifest(pluginPath: string, packageInfo: PackageInfo ); } + if ( + manifest.extraPublicDirs && + (!Array.isArray(manifest.extraPublicDirs) || + !manifest.extraPublicDirs.every((dir) => typeof dir === 'string')) + ) { + throw PluginDiscoveryError.invalidManifest( + manifestPath, + new Error( + `The "extraPublicDirs" in plugin manifest for "${manifest.id}" should be an array of strings.` + ) + ); + } + const expectedKibanaVersion = typeof manifest.kibanaVersion === 'string' && manifest.kibanaVersion ? manifest.kibanaVersion @@ -175,6 +193,7 @@ export async function parseManifest(pluginPath: string, packageInfo: PackageInfo optionalPlugins: Array.isArray(manifest.optionalPlugins) ? manifest.optionalPlugins : [], ui: includesUiPlugin, server: includesServerPlugin, + extraPublicDirs: manifest.extraPublicDirs, }; } diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 4fa4e1780e596..2ca5c9f6ed3c5 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -153,6 +153,14 @@ export interface PluginManifest { * Specifies whether plugin includes some server-side specific functionality. */ readonly server: boolean; + + /** + * Specifies directory names that can be imported by other ui-plugins built + * using the same instance of the @kbn/optimizer. A temporary measure we plan + * to replace with better mechanisms for sharing static code between plugins + * @deprecated + */ + readonly extraPublicDirs?: string[]; } /** diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index 32485f461f59b..5da2235828b5c 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -47,6 +47,7 @@ describe('getSortedObjectsForExport()', () => { id: '2', type: 'search', attributes: {}, + score: 1, references: [ { name: 'name', @@ -59,6 +60,7 @@ describe('getSortedObjectsForExport()', () => { id: '1', type: 'index-pattern', attributes: {}, + score: 1, references: [], }, ], @@ -133,6 +135,7 @@ describe('getSortedObjectsForExport()', () => { id: '2', type: 'search', attributes: {}, + score: 1, references: [ { name: 'name', @@ -145,6 +148,7 @@ describe('getSortedObjectsForExport()', () => { id: '1', type: 'index-pattern', attributes: {}, + score: 1, references: [], }, ], @@ -192,6 +196,7 @@ describe('getSortedObjectsForExport()', () => { id: '2', type: 'search', attributes: {}, + score: 1, references: [ { name: 'name', @@ -204,6 +209,7 @@ describe('getSortedObjectsForExport()', () => { id: '1', type: 'index-pattern', attributes: {}, + score: 1, references: [], }, ], @@ -279,6 +285,7 @@ describe('getSortedObjectsForExport()', () => { id: '2', type: 'search', attributes: {}, + score: 1, references: [ { name: 'name', @@ -291,6 +298,7 @@ describe('getSortedObjectsForExport()', () => { id: '1', type: 'index-pattern', attributes: {}, + score: 1, references: [], }, ], @@ -366,6 +374,7 @@ describe('getSortedObjectsForExport()', () => { id: '2', type: 'search', attributes: {}, + score: 1, references: [ { type: 'index-pattern', @@ -378,6 +387,7 @@ describe('getSortedObjectsForExport()', () => { id: '1', type: 'index-pattern', attributes: {}, + score: 1, references: [], }, ], @@ -405,6 +415,7 @@ describe('getSortedObjectsForExport()', () => { attributes: { name: 'baz', }, + score: 1, references: [], }, { @@ -413,6 +424,7 @@ describe('getSortedObjectsForExport()', () => { attributes: { name: 'foo', }, + score: 1, references: [], }, { @@ -421,6 +433,7 @@ describe('getSortedObjectsForExport()', () => { attributes: { name: 'bar', }, + score: 1, references: [], }, ], diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index cafaa5a3147db..6e985c25aeaef 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -116,8 +116,11 @@ async function fetchObjectsToExport({ } // sorts server-side by _id, since it's only available in fielddata - return findResponse.saved_objects.sort((a: SavedObject, b: SavedObject) => - a.id > b.id ? 1 : -1 + return ( + findResponse.saved_objects + // exclude the find-specific `score` property from the exported objects + .map(({ score, ...obj }) => obj) + .sort((a: SavedObject, b: SavedObject) => (a.id > b.id ? 1 : -1)) ); } else { throw Boom.badRequest('Either `type` or `objects` are required.'); diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts index 31bda1d6b9cbd..33e12dd4e517d 100644 --- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts @@ -79,6 +79,7 @@ describe('GET /api/saved_objects/_find', () => { timeFieldName: '@timestamp', notExpandable: true, attributes: {}, + score: 1, references: [], }, { @@ -88,6 +89,7 @@ describe('GET /api/saved_objects/_find', () => { timeFieldName: '@timestamp', notExpandable: true, attributes: {}, + score: 1, references: [], }, ], diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index d631ef9cb353c..ea749235cbb41 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -1939,7 +1939,7 @@ describe('SavedObjectsRepository', () => { { _index: '.kibana', _id: `${namespace ? `${namespace}:` : ''}config:6.0.0-alpha1`, - _score: 1, + _score: 2, ...mockVersionProps, _source: { namespace, @@ -1954,7 +1954,7 @@ describe('SavedObjectsRepository', () => { { _index: '.kibana', _id: `${namespace ? `${namespace}:` : ''}index-pattern:stocks-*`, - _score: 1, + _score: 3, ...mockVersionProps, _source: { namespace, @@ -1970,7 +1970,7 @@ describe('SavedObjectsRepository', () => { { _index: '.kibana', _id: `${NAMESPACE_AGNOSTIC_TYPE}:something`, - _score: 1, + _score: 4, ...mockVersionProps, _source: { type: NAMESPACE_AGNOSTIC_TYPE, @@ -2131,6 +2131,7 @@ describe('SavedObjectsRepository', () => { type: doc._source.type, ...mockTimestampFields, version: mockVersion, + score: doc._score, attributes: doc._source[doc._source.type], references: [], }); @@ -2153,6 +2154,7 @@ describe('SavedObjectsRepository', () => { type: doc._source.type, ...mockTimestampFields, version: mockVersion, + score: doc._score, attributes: doc._source[doc._source.type], references: [], }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 03538f2394845..40c5282a77e49 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -41,6 +41,7 @@ import { SavedObjectsBulkUpdateResponse, SavedObjectsCreateOptions, SavedObjectsFindResponse, + SavedObjectsFindResult, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, SavedObjectsBulkUpdateObject, @@ -674,8 +675,11 @@ export class SavedObjectsRepository { page, per_page: perPage, total: response.hits.total, - saved_objects: response.hits.hits.map((hit: SavedObjectsRawDoc) => - this._rawToSavedObject(hit) + saved_objects: response.hits.hits.map( + (hit: SavedObjectsRawDoc): SavedObjectsFindResult => ({ + ...this._rawToSavedObject(hit), + score: (hit as any)._score, + }) ), }; } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 8780f07cc3091..e15a92c92772f 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -79,6 +79,17 @@ export interface SavedObjectsBulkResponse { saved_objects: Array>; } +/** + * + * @public + */ +export interface SavedObjectsFindResult extends SavedObject { + /** + * The Elasticsearch `_score` of this result. + */ + score: number; +} + /** * Return type of the Saved Objects `find()` method. * @@ -88,7 +99,7 @@ export interface SavedObjectsBulkResponse { * @public */ export interface SavedObjectsFindResponse { - saved_objects: Array>; + saved_objects: Array>; total: number; per_page: number; page: number; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 78cc02d39e6c4..833c8918a0860 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1520,6 +1520,8 @@ export interface PluginInitializerContext { // @public export interface PluginManifest { readonly configPath: ConfigPath; + // @deprecated + readonly extraPublicDirs?: string[]; readonly id: PluginName; readonly kibanaVersion: string; readonly optionalPlugins: readonly PluginName[]; @@ -2037,11 +2039,16 @@ export interface SavedObjectsFindResponse { // (undocumented) per_page: number; // (undocumented) - saved_objects: Array>; + saved_objects: Array>; // (undocumented) total: number; } +// @public (undocumented) +export interface SavedObjectsFindResult extends SavedObject { + score: number; +} + // @public export interface SavedObjectsImportConflictError { // (undocumented) @@ -2549,8 +2556,8 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/legacy/types.ts:165:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:166:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:167:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:230:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:232:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:238:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:238:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:240:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts ``` 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 d1fb544de733c..745a3d1f0c830 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 @@ -235,6 +235,8 @@ kibana_vars=( xpack.security.session.lifespan xpack.security.loginAssistanceMessage xpack.security.loginHelp + xpack.spaces.enabled + xpack.spaces.maxSpaces telemetry.allowChangingOptInStatus telemetry.enabled telemetry.optIn diff --git a/src/dev/code_coverage/ingest_coverage/index_mapping.md b/src/dev/code_coverage/docs/code_coverage_job/kibana_code_coverage_index_mapping.md similarity index 97% rename from src/dev/code_coverage/ingest_coverage/index_mapping.md rename to src/dev/code_coverage/docs/code_coverage_job/kibana_code_coverage_index_mapping.md index c3b934eb35c5e..66654dd730518 100644 --- a/src/dev/code_coverage/ingest_coverage/index_mapping.md +++ b/src/dev/code_coverage/docs/code_coverage_job/kibana_code_coverage_index_mapping.md @@ -1,9 +1,12 @@ -# Create index mapping +# Index Mapping + +Execute the following in Kibana Dev Tools. -This is usually done in Kibana's dev tools ui. ``` - "mappings" : { +PUT /kibana_code_coverage +{ + "mappings" : { "properties" : { "@timestamp" : { "type" : "date" @@ -189,6 +192,7 @@ This is usually done in Kibana's dev tools ui. } } } +} ``` _The main portion of the above mapping, is the timestamp-date mapping._ \ No newline at end of file diff --git a/src/dev/code_coverage/ingest_coverage/ingest_code_coverage_readme.md b/src/dev/code_coverage/docs/ingest_code_coverage_readme.md similarity index 52% rename from src/dev/code_coverage/ingest_coverage/ingest_code_coverage_readme.md rename to src/dev/code_coverage/docs/ingest_code_coverage_readme.md index 0670780a7c03a..174f729180b77 100644 --- a/src/dev/code_coverage/ingest_coverage/ingest_code_coverage_readme.md +++ b/src/dev/code_coverage/docs/ingest_code_coverage_readme.md @@ -1,7 +1,18 @@ -# Convert Code Coverage Json Summary and Send to ES +# Massage and Ingest Code Coverage Json Summary and Send to ES +## Currently, we have 4 indexes +### 2 for the Code Coverage Job +https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/ +1. kibana_code_coverage +2. kibana_total_code_coverage + +### 2 for the R & D Job +https://kibana-ci.elastic.co/job/elastic+kibana+qa-research/ +1. qa_research_code_coverage +2. qa_research_total_code_coverage + ## How it works It starts with this jenkins pipeline file: @@ -19,6 +30,6 @@ From there, an event stream is created, that massages the data to an output form ## Configuration There is really only one config step. -The index [mapping](src/dev/code_coverage/ingest_coverage/index_mapping.md) for one of +The index [mapping](./code_coverage_job/kibana_code_coverage_index_mapping.md) for one of of the indexes has to be manually created. Currently, we just add it using Kibana's Dev Tools. \ No newline at end of file diff --git a/src/dev/code_coverage/docs/qa_research_job/put_qa_research_code_coverage_with_mapping.png b/src/dev/code_coverage/docs/qa_research_job/put_qa_research_code_coverage_with_mapping.png new file mode 100644 index 0000000000000..c2e4ee4546dfc Binary files /dev/null and b/src/dev/code_coverage/docs/qa_research_job/put_qa_research_code_coverage_with_mapping.png differ diff --git a/src/dev/code_coverage/docs/qa_research_job/qa_research_index_mapping.md b/src/dev/code_coverage/docs/qa_research_job/qa_research_index_mapping.md new file mode 100644 index 0000000000000..746f81c761fcc --- /dev/null +++ b/src/dev/code_coverage/docs/qa_research_job/qa_research_index_mapping.md @@ -0,0 +1,193 @@ +``` +PUT /qa_research_code_coverage +{ + "mappings" : { + "properties" : { + "@timestamp" : { + "type" : "date" + }, + "BUILD_ID" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "branches" : { + "properties" : { + "covered" : { + "type" : "long" + }, + "pct" : { + "type" : "long" + }, + "skipped" : { + "type" : "long" + }, + "total" : { + "type" : "long" + } + } + }, + "ciRunUrl" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "coveredFilePath" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "functions" : { + "properties" : { + "covered" : { + "type" : "long" + }, + "pct" : { + "type" : "long" + }, + "skipped" : { + "type" : "long" + }, + "total" : { + "type" : "long" + } + } + }, + "isTotal" : { + "type" : "boolean" + }, + "jsonSummaryPath" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "lines" : { + "properties" : { + "covered" : { + "type" : "long" + }, + "pct" : { + "type" : "long" + }, + "skipped" : { + "type" : "long" + }, + "total" : { + "type" : "long" + } + } + }, + "path" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "statements" : { + "properties" : { + "covered" : { + "type" : "long" + }, + "pct" : { + "type" : "long" + }, + "skipped" : { + "type" : "long" + }, + "total" : { + "type" : "long" + } + } + }, + "staticSiteUrl" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "testRunnerType" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "vcs" : { + "properties" : { + "author" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "branch" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "commitMsg" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "sha" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "vcsUrl" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + } + } + } + } + } +} +``` + +Execute the above in Kibana Dev Tools, eg: ![Index Mapping Screenshot](./put_qa_research_code_coverage_with_mapping.png "QA Research Code Coverage Index Mapping") \ No newline at end of file diff --git a/src/dev/code_coverage/ingest_coverage/teams_scripted_field.painless b/src/dev/code_coverage/docs/teams_scripted_field.painless similarity index 100% rename from src/dev/code_coverage/ingest_coverage/teams_scripted_field.painless rename to src/dev/code_coverage/docs/teams_scripted_field.painless diff --git a/src/es_archiver/es_archiver.ts b/src/es_archiver/es_archiver.ts index f36cbb3f516b9..e335652195b86 100644 --- a/src/es_archiver/es_archiver.ts +++ b/src/es_archiver/es_archiver.ts @@ -49,7 +49,7 @@ export class EsArchiver { this.client = client; this.dataDir = dataDir; this.log = log; - this.kbnClient = new KbnClient(log, [kibanaUrl]); + this.kbnClient = new KbnClient(log, { url: kibanaUrl }); } /** diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson index 81267cd514f1f..db19c937ca990 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_graph.hjson @@ -1,7 +1,7 @@ { // Adapted from Vega's https://vega.github.io/vega/examples/stacked-area-chart/ - $schema: https://vega.github.io/schema/vega/v3.0.json + $schema: https://vega.github.io/schema/vega/v5.json data: [ { name: table diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson index cef73c0155794..633b8658ad849 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_map_test.hjson @@ -1,7 +1,7 @@ # This graph creates a single rectangle for the whole graph on top of a map # Note that the actual map tiles are not loaded { - $schema: https://vega.github.io/schema/vega/v3.0.json + $schema: https://vega.github.io/schema/vega/v5.json config: { kibana: {type: "map", mapStyle: false} } diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson index 78e92a00384c6..77465c8b3f007 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_tooltip_test.hjson @@ -24,7 +24,7 @@ ] } ] - $schema: https://vega.github.io/schema/vega/v3.json + $schema: https://vega.github.io/schema/vega/v5.json marks: [ { from: {data: "table"} diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js index 485390dc50a79..17610702a0bc7 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js @@ -333,7 +333,7 @@ describe('VegaVisualizations', () => { vegaVis = new VegaVisualization(domNode, vis); const vegaParser = new VegaParser( `{ - "$schema": "https://vega.github.io/schema/vega/v3.json", + "$schema": "https://vega.github.io/schema/vega/v5.json", "marks": [ { "type": "text", @@ -366,11 +366,6 @@ describe('VegaVisualizations', () => { await vegaVis.render(vegaParser, vis.params, { data: true }); const vegaView = vegaVis._vegaView._view; expect(vegaView.height()).to.be(250.00000001); - - vegaView.height(250); - await vegaView.runAsync(); - // as soon as this test fails, the workaround with the subpixel value can be removed. - expect(vegaView.height()).to.be(0); } finally { vegaVis.destroy(); } diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson index fd7eb1ae7d878..2132b0f77e6bc 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vegalite_graph.hjson @@ -1,5 +1,5 @@ { - $schema: https://vega.github.io/schema/vega-lite/v2.json + $schema: https://vega.github.io/schema/vega-lite/v4.json data: { format: {property: "aggregations.time_buckets.buckets"} values: { diff --git a/src/legacy/ui/apm/index.js b/src/legacy/ui/apm/index.js index f1074091e2624..c43b7b01d1159 100644 --- a/src/legacy/ui/apm/index.js +++ b/src/legacy/ui/apm/index.js @@ -30,21 +30,15 @@ export function apmInit(config) { return apmEnabled ? `init(${config})` : ''; } -export function getApmConfig(app) { +export function getApmConfig(requestPath) { if (!apmEnabled) { return null; } - /** - * we use the injected app metadata from the server to extract the - * app id to be used for page-load transaction - */ - const appId = app.getId(); - const config = { ...getConfig('kibana-frontend'), ...{ active: true, - pageLoadTransactionName: appId, + pageLoadTransactionName: requestPath, }, }; /** diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index d60bf7df899e3..f7ee9aa056762 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -24,7 +24,7 @@ export const UI_EXPORT_DEFAULTS = { webpackNoParseRules: [ /node_modules[\/\\](angular|elasticsearch-browser)[\/\\]/, /node_modules[\/\\](mocha|moment)[\/\\]/, - /node_modules[\/\\]vega-lib[\/\\]build[\/\\]vega\.js$/, + /node_modules[\/\\]vega[\/\\]build[\/\\]vega\.js$/, ], webpackAliases: { diff --git a/src/legacy/ui/ui_render/bootstrap/app_bootstrap.js b/src/legacy/ui/ui_render/bootstrap/app_bootstrap.js index 0e6936dd64a15..19f75317883d7 100644 --- a/src/legacy/ui/ui_render/bootstrap/app_bootstrap.js +++ b/src/legacy/ui/ui_render/bootstrap/app_bootstrap.js @@ -22,9 +22,11 @@ import { createHash } from 'crypto'; import { readFile } from 'fs'; import { resolve } from 'path'; +import { kbnBundlesLoaderSource } from './kbn_bundles_loader_source'; + export class AppBootstrap { constructor({ templateData }) { - this.templateData = templateData; + this.templateData = { ...templateData, kbnBundlesLoaderSource }; this._rawTemplate = undefined; } diff --git a/src/legacy/ui/ui_render/bootstrap/kbn_bundles_loader_source.js b/src/legacy/ui/ui_render/bootstrap/kbn_bundles_loader_source.js new file mode 100644 index 0000000000000..cb5488118cc77 --- /dev/null +++ b/src/legacy/ui/ui_render/bootstrap/kbn_bundles_loader_source.js @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + kbnBundlesLoaderSource: `(${kbnBundlesLoader.toString()})();`, +}; + +function kbnBundlesLoader() { + var modules = {}; + + function has(prop) { + return Object.prototype.hasOwnProperty.call(modules, prop); + } + + function define(key, bundleRequire, bundleModuleKey) { + if (has(key)) { + throw new Error('__kbnBundles__ already has a module defined for "' + key + '"'); + } + + modules[key] = { + bundleRequire, + bundleModuleKey, + }; + } + + function get(key) { + if (!has(key)) { + throw new Error('__kbnBundles__ does not have a module defined for "' + key + '"'); + } + + return modules[key].bundleRequire(modules[key].bundleModuleKey); + } + + return { has: has, define: define, get: get }; +} diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index e8f05b46f7061..ca2e944489a73 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -3,6 +3,7 @@ window.__kbnStrictCsp__ = kbnCsp.strictCsp; window.__kbnDarkMode__ = {{darkMode}}; window.__kbnThemeVersion__ = "{{themeVersion}}"; window.__kbnPublicPath__ = {{publicPathMap}}; +window.__kbnBundles__ = {{kbnBundlesLoaderSource}} if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); @@ -78,12 +79,7 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { {{/each}} ], function () { {{#unless legacyBundlePath}} - if (!__kbnBundles__ || !__kbnBundles__['entry/core'] || typeof __kbnBundles__['entry/core'].__kbnBootstrap__ !== 'function') { - console.error('entry/core bundle did not load correctly'); - failure(); - } else { - __kbnBundles__['entry/core'].__kbnBootstrap__() - } + __kbnBundles__.get('entry/core/public').__kbnBootstrap__(); {{/unless}} load([ diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index b09d4861b343b..b4f8255297240 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -26,8 +26,6 @@ import { AppBootstrap } from './bootstrap'; import { getApmConfig } from '../apm'; import { DllCompiler } from '../../../optimize/dynamic_dll_plugin'; -const uniq = (...items) => Array.from(new Set(items)); - /** * @typedef {import('../../server/kbn_server').default} KbnServer * @typedef {import('../../server/kbn_server').ResponseToolkit} ResponseToolkit @@ -150,15 +148,7 @@ export function uiRenderMixin(kbnServer, server, config) { ]), ]; - const kpPluginIds = uniq( - // load these plugins first, they are "shared" and other bundles access their - // public/index exports without considering topographic sorting by plugin deps (for now) - 'kibanaUtils', - 'kibanaReact', - 'data', - 'esUiShared', - ...kbnServer.newPlatform.__internals.uiPlugins.public.keys() - ); + const kpPluginIds = Array.from(kbnServer.newPlatform.__internals.uiPlugins.public.keys()); const jsDependencyPaths = [ ...UiSharedDeps.jsDepFilenames.map( @@ -251,7 +241,7 @@ export function uiRenderMixin(kbnServer, server, config) { savedObjects.getClient(h.request) ); const vars = await legacy.getVars(app.getId(), h.request, { - apmConfig: getApmConfig(app), + apmConfig: getApmConfig(h.request.path), ...overrides, }); const content = await rendering.render(h.request, uiSettings, { diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index 28606b7dd9784..17968dd0281e6 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -32,8 +32,10 @@ export { export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; export { DashboardStart, DashboardUrlGenerator } from './plugin'; -export { DASHBOARD_APP_URL_GENERATOR } from './url_generator'; +export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator } from './url_generator'; export { addEmbeddableToDashboardUrl } from './url_utils/url_helper'; +export { SavedObjectDashboard } from './saved_dashboards'; +export { SavedDashboardPanel } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new DashboardPlugin(initializerContext); diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index d6805b2d94119..188de7fd857be 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -28,6 +28,7 @@ import { import { setStateToKbnUrl } from '../../kibana_utils/public'; import { UrlGeneratorsDefinition, UrlGeneratorState } from '../../share/public'; import { SavedObjectLoader } from '../../saved_objects/public'; +import { ViewMode } from '../../embeddable/public'; export const STATE_STORAGE_KEY = '_a'; export const GLOBAL_STATE_STORAGE_KEY = '_g'; @@ -73,6 +74,11 @@ export type DashboardAppLinkGeneratorState = UrlGeneratorState<{ * true is default */ preserveSavedFilters?: boolean; + + /** + * View mode of the dashboard. + */ + viewMode?: ViewMode; }>; export const createDashboardUrlGenerator = ( @@ -123,6 +129,7 @@ export const createDashboardUrlGenerator = ( cleanEmptyKeys({ query: state.query, filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), + viewMode: state.viewMode, }), { useHash }, `${appBasePath}#/${hash}` diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index f5df747f17e1e..3e5d96a4bc47b 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -7,5 +7,6 @@ "expressions", "uiActions" ], - "optionalPlugins": ["usageCollection"] + "optionalPlugins": ["usageCollection"], + "extraPublicDirs": ["common", "common/utils/abort_utils"] } diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx index 66adbfe9a6fc3..c66518c18a9ea 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/generic_combo_box.tsx @@ -59,6 +59,7 @@ export function GenericComboBox(props: GenericComboBoxProps) { options={euiOptions} selectedOptions={selectedEuiOptions} onChange={onComboBoxChange} + sortMatchesBy="startsWith" {...otherProps} /> ); diff --git a/src/plugins/data/server/search/i_search_context.ts b/src/plugins/data/server/field_formats/mocks.ts similarity index 79% rename from src/plugins/data/server/search/i_search_context.ts rename to src/plugins/data/server/field_formats/mocks.ts index 9d9de055d994f..ecfa33c86cf16 100644 --- a/src/plugins/data/server/search/i_search_context.ts +++ b/src/plugins/data/server/field_formats/mocks.ts @@ -17,10 +17,14 @@ * under the License. */ -import { Observable } from 'rxjs'; -import { CoreSetup, SharedGlobalConfig } from '../../../../core/server'; +export function createFieldFormatsSetupMock() { + return { + register: jest.fn(), + }; +} -export interface ISearchContext { - core: CoreSetup; - config$: Observable; +export function createFieldFormatsStartMock() { + return { + fieldFormatServiceFactory: jest.fn(), + }; } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 831d23864d228..16ac59e300237 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -172,8 +172,10 @@ export { ISearchOptions, IRequestTypesMap, IResponseTypesMap, - ISearchContext, - TSearchStrategyProvider, + ISearchSetup, + ISearchStart, + TStrategyTypes, + ISearchStrategy, getDefaultSearchParams, getTotalLoaded, } from './search'; diff --git a/src/plugins/data/server/search/strategy_types.ts b/src/plugins/data/server/mocks.ts similarity index 56% rename from src/plugins/data/server/search/strategy_types.ts rename to src/plugins/data/server/mocks.ts index 252e0c8f9e6c9..e2f2298234054 100644 --- a/src/plugins/data/server/search/strategy_types.ts +++ b/src/plugins/data/server/mocks.ts @@ -17,23 +17,24 @@ * under the License. */ -import { ES_SEARCH_STRATEGY } from '../../common/search/es_search'; +import { createSearchSetupMock, createSearchStartMock } from './search/mocks'; +import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; -/** - * Contains all known strategy type identifiers that will be used to map to - * request and response shapes. Plugins that wish to add their own custom search - * strategies should extend this type via: - * - * const MY_STRATEGY = 'MY_STRATEGY'; - * - * declare module 'src/plugins/search/server' { - * export interface IRequestTypesMap { - * [MY_STRATEGY]: IMySearchRequest; - * } - * - * export interface IResponseTypesMap { - * [MY_STRATEGY]: IMySearchResponse - * } - * } - */ -export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; +function createSetupContract() { + return { + search: createSearchSetupMock(), + fieldFormats: createFieldFormatsSetupMock(), + }; +} + +function createStartContract() { + return { + search: createSearchStartMock(), + fieldFormats: createFieldFormatsStartMock(), + }; +} + +export const dataPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 8c9d0df2ed894..0edce458f1c6b 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server'; import { ConfigSchema } from '../config'; import { IndexPatternsService } from './index_patterns'; -import { ISearchSetup } from './search'; +import { ISearchSetup, ISearchStart } from './search'; import { SearchService } from './search/search_service'; import { QueryService } from './query/query_service'; import { ScriptsService } from './scripts'; @@ -36,6 +36,7 @@ export interface DataPluginSetup { } export interface DataPluginStart { + search: ISearchStart; fieldFormats: FieldFormatsStart; } @@ -59,7 +60,10 @@ export class DataServerPlugin implements Plugin, + { usageCollection }: DataPluginSetupDependencies + ) { this.indexPatterns.setup(core); this.scriptsService.setup(core); this.queryService.setup(core); @@ -69,13 +73,14 @@ export class DataServerPlugin implements Plugin Promise.resolve({ total: 100, loaded: 0 })); -const mockDefaultSearchStrategyProvider = jest.fn(() => - Promise.resolve({ - search: mockDefaultSearch, - }) -); -const mockStrategies: TSearchStrategiesMap = { - [DEFAULT_SEARCH_STRATEGY]: mockDefaultSearchStrategyProvider, -}; - -describe('createApi', () => { - let api: IRouteHandlerSearchContext; - - beforeEach(() => { - api = createApi({ - caller: jest.fn(), - searchStrategies: mockStrategies, - }); - mockDefaultSearchStrategyProvider.mockClear(); - }); - - it('should default to DEFAULT_SEARCH_STRATEGY if none is provided', async () => { - await api.search({ - params: {}, - }); - expect(mockDefaultSearchStrategyProvider).toBeCalled(); - expect(mockDefaultSearch).toBeCalled(); - }); - - it('should throw if no provider is found for the given name', () => { - expect(api.search({}, {}, 'noneByThisName')).rejects.toThrowErrorMatchingInlineSnapshot( - `"No strategy found for noneByThisName"` - ); - }); - - it('logs the response if `debug` is set to `true`', async () => { - const spy = jest.spyOn(console, 'log'); - await api.search({ params: {} }); - - expect(spy).not.toBeCalled(); - - await api.search({ debug: true, params: {} }); - - expect(spy).toBeCalled(); - }); -}); diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts deleted file mode 100644 index 00665b21f2ba7..0000000000000 --- a/src/plugins/data/server/search/create_api.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { APICaller } from 'kibana/server'; -import { IRouteHandlerSearchContext } from './i_route_handler_search_context'; -import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; -import { TSearchStrategiesMap } from './i_search_strategy'; - -export function createApi({ - caller, - searchStrategies, -}: { - searchStrategies: TSearchStrategiesMap; - caller: APICaller; -}) { - const api: IRouteHandlerSearchContext = { - search: async (request, options, strategyName) => { - if (request.debug) { - // eslint-disable-next-line - console.log(JSON.stringify(request, null, 2)); - } - const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; - const strategyProvider = searchStrategies[name]; - if (!strategyProvider) { - throw new Error(`No strategy found for ${strategyName}`); - } - // Give providers access to other search strategies by injecting this function - const strategy = await strategyProvider(caller, api.search); - return strategy.search(request, options); - }, - cancel: async (id, strategyName) => { - const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; - const strategyProvider = searchStrategies[name]; - if (!strategyProvider) { - throw new Error(`No strategy found for ${strategyName}`); - } - const strategy = await strategyProvider(caller, api.search); - return strategy.cancel && strategy.cancel(id); - }, - }; - return api; -} diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index c4b8119f9e095..1155a5491e8f3 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -17,11 +17,11 @@ * under the License. */ -import { coreMock, pluginInitializerContextConfigMock } from '../../../../../core/server/mocks'; +import { RequestHandlerContext } from '../../../../../core/server'; +import { pluginInitializerContextConfigMock } from '../../../../../core/server/mocks'; import { esSearchStrategyProvider } from './es_search_strategy'; describe('ES search strategy', () => { - const mockCoreSetup = coreMock.createSetup(); const mockApiCaller = jest.fn().mockResolvedValue({ _shards: { total: 10, @@ -30,39 +30,26 @@ describe('ES search strategy', () => { successful: 7, }, }); - const mockSearch = jest.fn(); + const mockContext = { + core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } }, + }; const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; beforeEach(() => { mockApiCaller.mockClear(); - mockSearch.mockClear(); }); - it('returns a strategy with `search`', () => { - const esSearch = esSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + it('returns a strategy with `search`', async () => { + const esSearch = await esSearchStrategyProvider(mockConfig$); expect(typeof esSearch.search).toBe('function'); }); it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; - const esSearch = esSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await esSearchStrategyProvider(mockConfig$); - await esSearch.search({ params }); + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('search'); @@ -76,16 +63,9 @@ describe('ES search strategy', () => { it('calls the API caller with overridden defaults', async () => { const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' }; - const esSearch = esSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await esSearchStrategyProvider(mockConfig$); - await esSearch.search({ params }); + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('search'); @@ -97,16 +77,11 @@ describe('ES search strategy', () => { it('returns total, loaded, and raw response', async () => { const params = { index: 'logstash-*' }; - const esSearch = esSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await esSearchStrategyProvider(mockConfig$); - const response = await esSearch.search({ params }); + const response = await esSearch.search((mockContext as unknown) as RequestHandlerContext, { + params, + }); expect(response).toHaveProperty('total'); expect(response).toHaveProperty('loaded'); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 47cad7aa6b4d7..db08ddf920818 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -17,19 +17,18 @@ * under the License. */ import { first } from 'rxjs/operators'; -import { APICaller } from 'kibana/server'; +import { RequestHandlerContext, SharedGlobalConfig } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; +import { Observable } from 'rxjs'; import { ES_SEARCH_STRATEGY } from '../../../common/search'; -import { ISearchStrategy, TSearchStrategyProvider } from '../i_search_strategy'; -import { getDefaultSearchParams, getTotalLoaded, ISearchContext } from '..'; +import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..'; -export const esSearchStrategyProvider: TSearchStrategyProvider = ( - context: ISearchContext, - caller: APICaller +export const esSearchStrategyProvider = ( + config$: Observable ): ISearchStrategy => { return { - search: async (request, options) => { - const config = await context.config$.pipe(first()).toPromise(); + search: async (context: RequestHandlerContext, request, options) => { + const config = await config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); // Only default index pattern type is supported here. @@ -42,7 +41,12 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; + + const rawResponse = (await context.core.elasticsearch.legacy.client.callAsCurrentUser( + 'search', + params, + options + )) as SearchResponse; // The above query will either complete or timeout and throw an error. // There is no progress indication on this api. diff --git a/src/plugins/data/server/search/i_search.ts b/src/plugins/data/server/search/i_search.ts deleted file mode 100644 index fa4aa72ac7287..0000000000000 --- a/src/plugins/data/server/search/i_search.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; -import { TStrategyTypes } from './strategy_types'; -import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../common/search/es_search'; -import { IEsSearchRequest } from './es_search'; - -export interface ISearchOptions { - signal?: AbortSignal; -} - -export interface IRequestTypesMap { - [ES_SEARCH_STRATEGY]: IEsSearchRequest; - [key: string]: IKibanaSearchRequest; -} - -export interface IResponseTypesMap { - [ES_SEARCH_STRATEGY]: IEsSearchResponse; - [key: string]: IKibanaSearchResponse; -} - -export type ISearchGeneric = ( - request: IRequestTypesMap[T], - options?: ISearchOptions, - strategy?: T -) => Promise; - -export type ISearchCancelGeneric = ( - id: string, - strategy?: T -) => Promise; - -export type ISearch = ( - request: IRequestTypesMap[T], - options?: ISearchOptions -) => Promise; - -export type ISearchCancel = (id: string) => Promise; diff --git a/src/plugins/data/server/search/i_search_strategy.ts b/src/plugins/data/server/search/i_search_strategy.ts deleted file mode 100644 index 9b405034f883f..0000000000000 --- a/src/plugins/data/server/search/i_search_strategy.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { APICaller } from 'kibana/server'; -import { ISearch, ISearchCancel, ISearchGeneric } from './i_search'; -import { TStrategyTypes } from './strategy_types'; -import { ISearchContext } from './i_search_context'; - -/** - * Search strategy interface contains a search method that takes in - * a request and returns a promise that resolves to a response. - */ -export interface ISearchStrategy { - search: ISearch; - cancel?: ISearchCancel; -} - -/** - * Search strategy provider creates an instance of a search strategy with the request - * handler context bound to it. This way every search strategy can use - * whatever information they require from the request context. - */ -export type TSearchStrategyProviderEnhanced = ( - caller: APICaller, - search: ISearchGeneric -) => Promise>; - -/** - * Search strategy provider creates an instance of a search strategy with the request - * handler context bound to it. This way every search strategy can use - * whatever information they require from the request context. - */ -export type TSearchStrategyProvider = ( - context: ISearchContext, - caller: APICaller, - search: ISearchGeneric -) => ISearchStrategy; - -/** - * Extension point exposed for other plugins to register their own search - * strategies. - */ -export type TRegisterSearchStrategyProvider = ( - opaqueId: symbol, - name: T, - searchStrategyProvider: TSearchStrategyProvider -) => void; - -export type TSearchStrategiesMap = { - [K in TStrategyTypes]?: TSearchStrategyProviderEnhanced; -}; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index e08eba1cad831..882f56e83d4ca 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -17,20 +17,16 @@ * under the License. */ -export { ISearchSetup } from './i_search_setup'; - -export { ISearchContext } from './i_search_context'; - export { ISearch, ISearchCancel, ISearchOptions, IRequestTypesMap, IResponseTypesMap, -} from './i_search'; - -export { TStrategyTypes } from './strategy_types'; - -export { TSearchStrategyProvider } from './i_search_strategy'; + ISearchSetup, + ISearchStart, + TStrategyTypes, + ISearchStrategy, +} from './types'; export { getDefaultSearchParams, getTotalLoaded } from './es_search'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 136e7a1d580c9..0aab466a9a0d9 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,10 +17,14 @@ * under the License. */ -export const searchSetupMock = { - registerSearchStrategyContext: jest.fn(), - registerSearchStrategyProvider: jest.fn(), - __LEGACY: { - search: jest.fn(), - }, -}; +export function createSearchSetupMock() { + return { + registerSearchStrategy: jest.fn(), + }; +} + +export function createSearchStartMock() { + return { + getSearchStrategy: jest.fn(), + }; +} diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index f5e6507d977cd..4ef67de93e454 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -17,36 +17,26 @@ * under the License. */ -import { httpServiceMock, httpServerMock } from '../../../../../src/core/server/mocks'; +import { CoreSetup, RequestHandlerContext } from '../../../../../src/core/server'; +import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks'; import { registerSearchRoute } from './routes'; -import { IRouter, ScopedClusterClient, RequestHandlerContext } from 'kibana/server'; +import { DataPluginStart } from '../plugin'; +import { dataPluginMock } from '../mocks'; describe('Search service', () => { - let routerMock: jest.Mocked; + let mockDataStart: MockedKeys; + let mockCoreSetup: MockedKeys>; beforeEach(() => { - routerMock = httpServiceMock.createRouter(); - }); - - it('registers a post route', async () => { - registerSearchRoute(routerMock); - expect(routerMock.post).toBeCalled(); + mockDataStart = dataPluginMock.createStartContract(); + mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart }); }); it('handler calls context.search.search with the given request and strategy', async () => { const mockSearch = jest.fn().mockResolvedValue('yay'); - const mockContext = { - core: { - elasticsearch: { - legacy: { - client: {} as ScopedClusterClient, - }, - }, - }, - search: { - search: mockSearch, - }, - }; + mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch }); + + const mockContext = {}; const mockBody = { params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ @@ -55,13 +45,15 @@ describe('Search service', () => { }); const mockResponse = httpServerMock.createResponseFactory(); - registerSearchRoute(routerMock); - const handler = routerMock.post.mock.calls[0][1]; + registerSearchRoute(mockCoreSetup); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + expect(mockDataStart.search.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy); expect(mockSearch).toBeCalled(); - expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); - expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy); + expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody); expect(mockResponse.ok).toBeCalled(); expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' }); }); @@ -73,18 +65,9 @@ describe('Search service', () => { error: 'oops', }, }); - const mockContext = { - core: { - elasticsearch: { - legacy: { - client: {} as ScopedClusterClient, - }, - }, - }, - search: { - search: mockSearch, - }, - }; + mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch }); + + const mockContext = {}; const mockBody = { params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ @@ -93,13 +76,15 @@ describe('Search service', () => { }); const mockResponse = httpServerMock.createResponseFactory(); - registerSearchRoute(routerMock); - const handler = routerMock.post.mock.calls[0][1]; + registerSearchRoute(mockCoreSetup); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const handler = mockRouter.post.mock.calls[0][1]; await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); + expect(mockDataStart.search.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy); expect(mockSearch).toBeCalled(); - expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); - expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy); + expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody); expect(mockResponse.customError).toBeCalled(); const error: any = mockResponse.customError.mock.calls[0][0]; expect(error.body.message).toBe('oh no'); diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index b90d7d4ff80ce..7b6c045b0908c 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -18,10 +18,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../../../core/server'; +import { CoreSetup } from '../../../../core/server'; import { getRequestAbortedSignal } from '../lib'; +import { DataPluginStart } from '../plugin'; + +export function registerSearchRoute(core: CoreSetup): void { + const router = core.http.createRouter(); -export function registerSearchRoute(router: IRouter): void { router.post( { path: '/internal/search/{strategy}', @@ -38,8 +41,11 @@ export function registerSearchRoute(router: IRouter): void { const { strategy } = request.params; const signal = getRequestAbortedSignal(request.events.aborted$); + const [, , selfStart] = await core.getStartServices(); + const searchStrategy = selfStart.search.getSearchStrategy(strategy); + try { - const response = await context.search!.search(searchRequest, { signal }, strategy); + const response = await searchStrategy.search(context, searchRequest, { signal }); return res.ok({ body: response }); } catch (err) { return res.customError({ @@ -69,8 +75,13 @@ export function registerSearchRoute(router: IRouter): void { }, async (context, request, res) => { const { strategy, id } = request.params; + + const [, , selfStart] = await core.getStartServices(); + const searchStrategy = selfStart.search.getSearchStrategy(strategy); + if (!searchStrategy.cancel) return res.ok(); + try { - await context.search!.cancel(id, strategy); + await searchStrategy.cancel(context, id); return res.ok(); } catch (err) { return res.customError({ diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index fa659756c1273..25143fa09e6bf 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -21,27 +21,28 @@ import { coreMock } from '../../../../core/server/mocks'; import { SearchService } from './search_service'; import { CoreSetup } from '../../../../core/server'; - -const mockSearchApi = { search: jest.fn() }; -jest.mock('./create_api', () => ({ - createApi: () => mockSearchApi, -})); +import { DataPluginStart } from '../plugin'; describe('Search service', () => { let plugin: SearchService; - let mockCoreSetup: MockedKeys; + let mockCoreSetup: MockedKeys>; beforeEach(() => { plugin = new SearchService(coreMock.createPluginInitializerContext({})); mockCoreSetup = coreMock.createSetup(); - mockSearchApi.search.mockClear(); }); describe('setup()', () => { it('exposes proper contract', async () => { const setup = plugin.setup(mockCoreSetup); - expect(setup).toHaveProperty('registerSearchStrategyContext'); - expect(setup).toHaveProperty('registerSearchStrategyProvider'); + expect(setup).toHaveProperty('registerSearchStrategy'); + }); + }); + + describe('start()', () => { + it('exposes proper contract', async () => { + const setup = plugin.start(); + expect(setup).toHaveProperty('getSearchStrategy'); }); }); }); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 1c267c32ebc37..df809b425eb9e 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -17,82 +17,52 @@ * under the License. */ +import { Plugin, PluginInitializerContext, CoreSetup } from '../../../../core/server'; import { - PluginInitializerContext, - Plugin, - CoreSetup, - IContextContainer, -} from '../../../../core/server'; -import { registerSearchRoute } from './routes'; -import { ISearchSetup } from './i_search_setup'; -import { createApi } from './create_api'; -import { + ISearchSetup, + ISearchStart, TSearchStrategiesMap, - TSearchStrategyProvider, - TRegisterSearchStrategyProvider, -} from './i_search_strategy'; -import { IRouteHandlerSearchContext } from './i_route_handler_search_context'; + TRegisterSearchStrategy, + TGetSearchStrategy, +} from './types'; +import { registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search'; - import { searchSavedObjectType } from '../saved_objects'; +import { DataPluginStart } from '../plugin'; -declare module 'kibana/server' { - interface RequestHandlerContext { - search?: IRouteHandlerSearchContext; - } -} - -export class SearchService implements Plugin { +export class SearchService implements Plugin { private searchStrategies: TSearchStrategiesMap = {}; - private contextContainer?: IContextContainer>; - constructor(private initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup): ISearchSetup { - const router = core.http.createRouter(); - registerSearchRoute(router); - - this.contextContainer = core.context.createContextContainer(); - + public setup(core: CoreSetup): ISearchSetup { core.savedObjects.registerType(searchSavedObjectType); - core.http.registerRouteHandlerContext<'search'>('search', (context) => { - return createApi({ - caller: context.core.elasticsearch.legacy.client.callAsCurrentUser, - searchStrategies: this.searchStrategies, - }); - }); - - const registerSearchStrategyProvider: TRegisterSearchStrategyProvider = ( - plugin, - name, - strategyProvider - ) => { - this.searchStrategies[name] = this.contextContainer!.createHandler(plugin, strategyProvider); - }; - - const api: ISearchSetup = { - registerSearchStrategyContext: this.contextContainer!.registerContext, - registerSearchStrategyProvider, - }; - - api.registerSearchStrategyContext(this.initializerContext.opaqueId, 'core', () => core); - api.registerSearchStrategyContext( - this.initializerContext.opaqueId, - 'config$', - () => this.initializerContext.config.legacy.globalConfig$ - ); - - api.registerSearchStrategyProvider( - this.initializerContext.opaqueId, + this.registerSearchStrategy( ES_SEARCH_STRATEGY, - esSearchStrategyProvider + esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$) ); - return api; + registerSearchRoute(core); + + return { registerSearchStrategy: this.registerSearchStrategy }; + } + + public start(): ISearchStart { + return { getSearchStrategy: this.getSearchStrategy }; } - public start() {} public stop() {} + + private registerSearchStrategy: TRegisterSearchStrategy = (name, strategy) => { + this.searchStrategies[name] = strategy; + }; + + private getSearchStrategy: TGetSearchStrategy = (name) => { + const strategy = this.searchStrategies[name]; + if (!strategy) { + throw new Error(`Search strategy ${name} not found`); + } + return strategy; + }; } diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts new file mode 100644 index 0000000000000..dea325cc063bb --- /dev/null +++ b/src/plugins/data/server/search/types.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RequestHandlerContext } from '../../../../core/server'; +import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; +import { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from './es_search'; + +export interface ISearchSetup { + /** + * Extension point exposed for other plugins to register their own search + * strategies. + */ + registerSearchStrategy: TRegisterSearchStrategy; +} + +export interface ISearchStart { + /** + * Get other registered search strategies. For example, if a new strategy needs to use the + * already-registered ES search strategy, it can use this function to accomplish that. + */ + getSearchStrategy: TGetSearchStrategy; +} + +export interface ISearchOptions { + /** + * An `AbortSignal` that allows the caller of `search` to abort a search request. + */ + signal?: AbortSignal; +} + +/** + * Contains all known strategy type identifiers that will be used to map to + * request and response shapes. Plugins that wish to add their own custom search + * strategies should extend this type via: + * + * const MY_STRATEGY = 'MY_STRATEGY'; + * + * declare module 'src/plugins/search/server' { + * export interface IRequestTypesMap { + * [MY_STRATEGY]: IMySearchRequest; + * } + * + * export interface IResponseTypesMap { + * [MY_STRATEGY]: IMySearchResponse + * } + * } + */ +export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; + +/** + * The map of search strategy IDs to the corresponding request type definitions. + */ +export interface IRequestTypesMap { + [ES_SEARCH_STRATEGY]: IEsSearchRequest; + [key: string]: IKibanaSearchRequest; +} + +/** + * The map of search strategy IDs to the corresponding response type definitions. + */ +export interface IResponseTypesMap { + [ES_SEARCH_STRATEGY]: IEsSearchResponse; + [key: string]: IKibanaSearchResponse; +} + +export type ISearch = ( + context: RequestHandlerContext, + request: IRequestTypesMap[T], + options?: ISearchOptions +) => Promise; + +export type ISearchCancel = ( + context: RequestHandlerContext, + id: string +) => Promise; + +/** + * Search strategy interface contains a search method that takes in a request and returns a promise + * that resolves to a response. + */ +export interface ISearchStrategy { + search: ISearch; + cancel?: ISearchCancel; +} + +export type TRegisterSearchStrategy = ( + name: T, + searchStrategy: ISearchStrategy +) => void; + +export type TGetSearchStrategy = (name: T) => ISearchStrategy; + +export type TSearchStrategiesMap = { + [K in TStrategyTypes]?: ISearchStrategy; +}; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 2d4185415b9d5..d825153a7aa12 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -48,7 +48,6 @@ import { GetResponse } from 'elasticsearch'; import { GetScriptParams } from 'elasticsearch'; import { GetSourceParams } from 'elasticsearch'; import { GetTemplateParams } from 'elasticsearch'; -import { IContextProvider as IContextProvider_2 } from 'kibana/server'; import { IncomingHttpHeaders } from 'http'; import { IndexDocumentParams } from 'elasticsearch'; import { IndicesAnalyzeParams } from 'elasticsearch'; @@ -490,7 +489,7 @@ export class IndexPatternsFetcher { // Warning: (ae-missing-release-tag) "IRequestTypesMap" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface IRequestTypesMap { // Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts // @@ -505,7 +504,7 @@ export interface IRequestTypesMap { // Warning: (ae-missing-release-tag) "IResponseTypesMap" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export interface IResponseTypesMap { // Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts // @@ -517,35 +516,48 @@ export interface IResponseTypesMap { [ES_SEARCH_STRATEGY]: IEsSearchResponse; } -// Warning: (ae-forgotten-export) The symbol "TStrategyTypes" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ISearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ISearch = (request: IRequestTypesMap[T], options?: ISearchOptions) => Promise; +export type ISearch = (context: RequestHandlerContext, request: IRequestTypesMap[T], options?: ISearchOptions) => Promise; // Warning: (ae-missing-release-tag) "ISearchCancel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ISearchCancel = (id: string) => Promise; +export type ISearchCancel = (context: RequestHandlerContext, id: string) => Promise; -// Warning: (ae-missing-release-tag) "ISearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ISearchContext { - // (undocumented) - config$: Observable; - // Warning: (ae-forgotten-export) The symbol "CoreSetup" needs to be exported by the entry point index.d.ts - // - // (undocumented) - core: CoreSetup; +export interface ISearchOptions { + signal?: AbortSignal; } -// Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "ISearchSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ISearchOptions { +export interface ISearchSetup { + // Warning: (ae-forgotten-export) The symbol "TRegisterSearchStrategy" needs to be exported by the entry point index.d.ts + registerSearchStrategy: TRegisterSearchStrategy; +} + +// Warning: (ae-missing-release-tag) "ISearchStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISearchStart { + // Warning: (ae-forgotten-export) The symbol "TGetSearchStrategy" needs to be exported by the entry point index.d.ts + getSearchStrategy: TGetSearchStrategy; +} + +// Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface ISearchStrategy { // (undocumented) - signal?: AbortSignal; + cancel?: ISearchCancel; + // (undocumented) + search: ISearch; } // @public (undocumented) @@ -612,21 +624,23 @@ export function parseInterval(interval: string): moment.Duration | null; export class Plugin implements Plugin_2 { // Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts constructor(initializerContext: PluginInitializerContext); + // Warning: (ae-forgotten-export) The symbol "CoreSetup" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "DataPluginSetupDependencies" needs to be exported by the entry point index.d.ts // // (undocumented) - setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): { + setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): { + search: ISearchSetup; fieldFormats: { register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; - search: ISearchSetup; }; // Warning: (ae-forgotten-export) The symbol "CoreStart" needs to be exported by the entry point index.d.ts // // (undocumented) start(core: CoreStart): { + search: ISearchStart; fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; }; // (undocumented) @@ -656,6 +670,8 @@ export interface PluginStart { // // (undocumented) fieldFormats: FieldFormatsStart; + // (undocumented) + search: ISearchStart; } // Warning: (ae-missing-release-tag) "Query" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -713,12 +729,10 @@ export interface TimeRange { to: string; } -// Warning: (ae-forgotten-export) The symbol "ISearchGeneric" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "ISearchStrategy" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "TSearchStrategyProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "TStrategyTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export type TSearchStrategyProvider = (context: ISearchContext, caller: APICaller_2, search: ISearchGeneric) => ISearchStrategy; +export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; // Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -778,13 +792,12 @@ export const UI_SETTINGS: { // src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/plugin.ts:66:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/embeddable/kibana.json b/src/plugins/embeddable/kibana.json index 535527b4d09db..06b0e88da334f 100644 --- a/src/plugins/embeddable/kibana.json +++ b/src/plugins/embeddable/kibana.json @@ -6,5 +6,8 @@ "requiredPlugins": [ "inspector", "uiActions" + ], + "extraPublicDirs": [ + "public/lib/test_samples" ] } diff --git a/src/plugins/es_ui_shared/kibana.json b/src/plugins/es_ui_shared/kibana.json index 5f772b3c83da9..980f43ea46a68 100644 --- a/src/plugins/es_ui_shared/kibana.json +++ b/src/plugins/es_ui_shared/kibana.json @@ -2,5 +2,13 @@ "id": "esUiShared", "version": "kibana", "ui": true, - "server": true + "server": true, + "extraPublicDirs": [ + "static/ace_x_json/hooks", + "static/validators/string", + "static/forms/hook_form_lib", + "static/forms/helpers", + "static/forms/components", + "static/forms/helpers/field_validators/types" + ] } diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index 5d2112103e94d..4774c69cc29ff 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -5,5 +5,6 @@ "ui": true, "requiredPlugins": [ "bfetch" - ] + ], + "extraPublicDirs": ["common", "common/fonts"] } diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 1cce11aea37fb..c1f7fcb75b149 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -420,7 +420,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Airport Connections (Hover Over Airport)', }), visState: - '{"aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v3.0.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 25, longitude: -70, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_flights\\n %context%: true\\n // Uncomment to enable time filtering\\n // %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n origins: {\\n terms: {field: \\"OriginAirportID\\", size: 10000}\\n aggs: {\\n originLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\"OriginLocation\\", \\"Origin\\"]\\n }\\n }\\n }\\n distinations: {\\n terms: {field: \\"DestAirportID\\", size: 10000}\\n aggs: {\\n destLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\"DestLocation\\"]\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.origins.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n originLocation.hits.hits[0]._source.OriginLocation.lon\\n originLocation.hits.hits[0]._source.OriginLocation.lat\\n ]\\n }\\n ]\\n }\\n {\\n name: selectedDatum\\n on: [\\n {trigger: \\"!selected\\", remove: true}\\n {trigger: \\"selected\\", insert: \\"selected\\"}\\n ]\\n }\\n ]\\n signals: [\\n {\\n name: selected\\n value: null\\n on: [\\n {events: \\"@airport:mouseover\\", update: \\"datum\\"}\\n {events: \\"@airport:mouseout\\", update: \\"null\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: airportSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n {signal: \\"zoom*zoom*0.2+1\\"}\\n {signal: \\"zoom*zoom*10+1\\"}\\n ]\\n }\\n ]\\n marks: [\\n {\\n type: group\\n from: {\\n facet: {\\n name: facetedDatum\\n data: selectedDatum\\n field: distinations.buckets\\n }\\n }\\n data: [\\n {\\n name: facetDatumElems\\n source: facetedDatum\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n destLocation.hits.hits[0]._source.DestLocation.lon\\n destLocation.hits.hits[0]._source.DestLocation.lat\\n ]\\n }\\n {type: \\"formula\\", expr: \\"{x:parent.x, y:parent.y}\\", as: \\"source\\"}\\n {type: \\"formula\\", expr: \\"{x:datum.x, y:datum.y}\\", as: \\"target\\"}\\n {type: \\"linkpath\\", shape: \\"diagonal\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: lineThickness\\n type: linear\\n domain: {data: \\"facetDatumElems\\", field: \\"doc_count\\"}\\n range: [1, 8]\\n }\\n {\\n name: lineOpacity\\n type: linear\\n domain: {data: \\"facetDatumElems\\", field: \\"doc_count\\"}\\n range: [0.2, 0.8]\\n }\\n ]\\n marks: [\\n {\\n from: {data: \\"facetDatumElems\\"}\\n type: path\\n interactive: false\\n encode: {\\n update: {\\n path: {field: \\"path\\"}\\n stroke: {value: \\"black\\"}\\n strokeWidth: {scale: \\"lineThickness\\", field: \\"doc_count\\"}\\n strokeOpacity: {scale: \\"lineOpacity\\", field: \\"doc_count\\"}\\n }\\n }\\n }\\n ]\\n }\\n {\\n name: airport\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n size: {scale: \\"airportSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n tooltip: {\\n signal: \\"{title: datum.originLocation.hits.hits[0]._source.Origin + \' (\' + datum.key + \')\', connnections: length(datum.distinations.buckets), flights: datum.doc_count}\\"\\n }\\n }\\n }\\n }\\n ]\\n}"},"title":"[Flights] Airport Connections (Hover Over Airport)","type":"vega"}', + '{"aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 25, longitude: -70, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_flights\\n %context%: true\\n // Uncomment to enable time filtering\\n // %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n origins: {\\n terms: {field: \\"OriginAirportID\\", size: 10000}\\n aggs: {\\n originLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\"OriginLocation\\", \\"Origin\\"]\\n }\\n }\\n }\\n distinations: {\\n terms: {field: \\"DestAirportID\\", size: 10000}\\n aggs: {\\n destLocation: {\\n top_hits: {\\n size: 1\\n _source: {\\n includes: [\\"DestLocation\\"]\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.origins.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n originLocation.hits.hits[0]._source.OriginLocation.lon\\n originLocation.hits.hits[0]._source.OriginLocation.lat\\n ]\\n }\\n ]\\n }\\n {\\n name: selectedDatum\\n on: [\\n {trigger: \\"!selected\\", remove: true}\\n {trigger: \\"selected\\", insert: \\"selected\\"}\\n ]\\n }\\n ]\\n signals: [\\n {\\n name: selected\\n value: null\\n on: [\\n {events: \\"@airport:mouseover\\", update: \\"datum\\"}\\n {events: \\"@airport:mouseout\\", update: \\"null\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: airportSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n {signal: \\"zoom*zoom*0.2+1\\"}\\n {signal: \\"zoom*zoom*10+1\\"}\\n ]\\n }\\n ]\\n marks: [\\n {\\n type: group\\n from: {\\n facet: {\\n name: facetedDatum\\n data: selectedDatum\\n field: distinations.buckets\\n }\\n }\\n data: [\\n {\\n name: facetDatumElems\\n source: facetedDatum\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n destLocation.hits.hits[0]._source.DestLocation.lon\\n destLocation.hits.hits[0]._source.DestLocation.lat\\n ]\\n }\\n {type: \\"formula\\", expr: \\"{x:parent.x, y:parent.y}\\", as: \\"source\\"}\\n {type: \\"formula\\", expr: \\"{x:datum.x, y:datum.y}\\", as: \\"target\\"}\\n {type: \\"linkpath\\", shape: \\"diagonal\\"}\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: lineThickness\\n type: log\\n clamp: true\\n range: [1, 8]\\n }\\n {\\n name: lineOpacity\\n type: log\\n clamp: true\\n range: [0.2, 0.8]\\n }\\n ]\\n marks: [\\n {\\n from: {data: \\"facetDatumElems\\"}\\n type: path\\n interactive: false\\n encode: {\\n update: {\\n path: {field: \\"path\\"}\\n stroke: {value: \\"black\\"}\\n strokeWidth: {scale: \\"lineThickness\\", field: \\"doc_count\\"}\\n strokeOpacity: {scale: \\"lineOpacity\\", field: \\"doc_count\\"}\\n }\\n }\\n }\\n ]\\n }\\n {\\n name: airport\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n size: {scale: \\"airportSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n tooltip: {\\n signal: \\"{title: datum.originLocation.hits.hits[0]._source.Origin + \' (\' + datum.key + \')\', connnections: length(datum.distinations.buckets), flights: datum.doc_count}\\"\\n }\\n }\\n }\\n }\\n ]\\n}"},"title":"[Flights] Airport Connections (Hover Over Airport)","type":"vega"}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 883108651bfc5..0620e93118b8d 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -145,7 +145,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Logs] File Type Scatter Plot', }), visState: - '{"title":"[Logs] File Type Scatter Plot","type":"vega","params":{"spec":"{\\n $schema: \\"https://vega.github.io/schema/vega-lite/v2.json\\"\\n // Use points for drawing to actually create a scatterplot\\n mark: point\\n // Specify where to load data from\\n data: {\\n // By using an object to the url parameter we will\\n // construct an Elasticsearch query\\n url: {\\n // Context == true means filters of the dashboard will be taken into account\\n %context%: true\\n // Specify on which field the time picker should operate\\n %timefield%: timestamp\\n // Specify the index pattern to load data from\\n index: kibana_sample_data_logs\\n // This body will be send to Elasticsearch\'s _search endpoint\\n // You can use everything the ES Query DSL supports here\\n body: {\\n // Set the size to load 10000 documents\\n size: 10000,\\n // Just ask for the fields we actually need for visualization\\n _source: [\\"timestamp\\", \\"bytes\\", \\"extension\\"]\\n }\\n }\\n // Tell Vega, that the array of data will be inside hits.hits of the response\\n // since the result returned from Elasticsearch fill have a format like:\\n // {\\n // hits: {\\n // total: 42000,\\n // max_score: 2,\\n // hits: [\\n // < our individual documents >\\n // ]\\n // }\\n // }\\n format: { property: \\"hits.hits\\" }\\n }\\n // You can do transformation and calculation of the data before drawing it\\n transform: [\\n // Since timestamp is a string value, we need to convert it to a unix timestamp\\n // so that Vega can work on it properly.\\n {\\n // Convert _source.timestamp field to a date\\n calculate: \\"toDate(datum._source[\'timestamp\'])\\"\\n // Store the result in a field named \\"time\\" in the object\\n as: \\"time\\"\\n }\\n ]\\n // Specify what data will be drawn on which axis\\n encoding: {\\n x: {\\n // Draw the time field on the x-axis in temporal mode (i.e. as a time axis)\\n field: time\\n type: temporal\\n // Hide the axis label for the x-axis\\n axis: { title: false }\\n }\\n y: {\\n // Draw the bytes of each document on the y-axis\\n field: _source.bytes\\n // Mark the y-axis as quantitative\\n type: quantitative\\n // Specify the label for this axis\\n axis: { title: \\"Transferred bytes\\" }\\n }\\n color: {\\n // Make the color of each point depend on the _source.extension field\\n field: _source.extension\\n // Treat different values as completely unrelated values to each other.\\n // You could switch this to quantitative if you have a numeric field and\\n // want to create a color scale from one color to another depending on that\\n // field\'s value.\\n type: nominal\\n // Rename the legend title so it won\'t just state: \\"_source.extension\\"\\n legend: { title: \'File type\' }\\n }\\n shape: {\\n // Also make the shape of each point dependent on the extension.\\n field: _source.extension\\n type: nominal\\n }\\n }\\n}"},"aggs":[]}', + '{"title":"[Logs] File Type Scatter Plot","type":"vega","params":{"spec":"{\\n $schema: \\"https://vega.github.io/schema/vega-lite/v4.json\\"\\n // Use points for drawing to actually create a scatterplot\\n mark: point\\n // Specify where to load data from\\n data: {\\n // By using an object to the url parameter we will\\n // construct an Elasticsearch query\\n url: {\\n // Context == true means filters of the dashboard will be taken into account\\n %context%: true\\n // Specify on which field the time picker should operate\\n %timefield%: timestamp\\n // Specify the index pattern to load data from\\n index: kibana_sample_data_logs\\n // This body will be send to Elasticsearch\'s _search endpoint\\n // You can use everything the ES Query DSL supports here\\n body: {\\n // Set the size to load 10000 documents\\n size: 10000,\\n // Just ask for the fields we actually need for visualization\\n _source: [\\"timestamp\\", \\"bytes\\", \\"extension\\"]\\n }\\n }\\n // Tell Vega, that the array of data will be inside hits.hits of the response\\n // since the result returned from Elasticsearch fill have a format like:\\n // {\\n // hits: {\\n // total: 42000,\\n // max_score: 2,\\n // hits: [\\n // < our individual documents >\\n // ]\\n // }\\n // }\\n format: { property: \\"hits.hits\\" }\\n }\\n // You can do transformation and calculation of the data before drawing it\\n transform: [\\n // Since timestamp is a string value, we need to convert it to a unix timestamp\\n // so that Vega can work on it properly.\\n {\\n // Convert _source.timestamp field to a date\\n calculate: \\"toDate(datum._source[\'timestamp\'])\\"\\n // Store the result in a field named \\"time\\" in the object\\n as: \\"time\\"\\n }\\n ]\\n // Specify what data will be drawn on which axis\\n encoding: {\\n x: {\\n // Draw the time field on the x-axis in temporal mode (i.e. as a time axis)\\n field: time\\n type: temporal\\n // Hide the axis label for the x-axis\\n axis: { title: false }\\n }\\n y: {\\n // Draw the bytes of each document on the y-axis\\n field: _source.bytes\\n // Mark the y-axis as quantitative\\n type: quantitative\\n // Specify the label for this axis\\n axis: { title: \\"Transferred bytes\\" }\\n }\\n color: {\\n // Make the color of each point depend on the _source.extension field\\n field: _source.extension\\n // Treat different values as completely unrelated values to each other.\\n // You could switch this to quantitative if you have a numeric field and\\n // want to create a color scale from one color to another depending on that\\n // field\'s value.\\n type: nominal\\n // Rename the legend title so it won\'t just state: \\"_source.extension\\"\\n legend: { title: \'File type\' }\\n }\\n shape: {\\n // Also make the shape of each point dependent on the extension.\\n field: _source.extension\\n type: nominal\\n }\\n }\\n}"},"aggs":[]}', uiStateJSON: '{}', description: '', version: 1, @@ -166,7 +166,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Logs] Source and Destination Sankey Chart', }), visState: - '{"title":"[Logs] Source and Destination Sankey Chart","type":"vega","params":{"spec":"{ \\n $schema: https://vega.github.io/schema/vega/v3.0.json\\n data: [\\n\\t{\\n \\t// query ES based on the currently selected time range and filter string\\n \\tname: rawData\\n \\turl: {\\n \\t%context%: true\\n \\t%timefield%: timestamp\\n \\tindex: kibana_sample_data_logs\\n \\tbody: {\\n \\tsize: 0\\n \\taggs: {\\n \\ttable: {\\n \\tcomposite: {\\n \\tsize: 10000\\n \\tsources: [\\n \\t{\\n \\tstk1: {\\n \\tterms: {field: \\"geo.src\\"}\\n \\t}\\n \\t}\\n \\t{\\n \\tstk2: {\\n \\tterms: {field: \\"geo.dest\\"}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t// From the result, take just the data we are interested in\\n \\tformat: {property: \\"aggregations.table.buckets\\"}\\n \\t// Convert key.stk1 -> stk1 for simpler access below\\n \\ttransform: [\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk1\\", as: \\"stk1\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk2\\", as: \\"stk2\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.doc_count\\", as: \\"size\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: nodes\\n \\tsource: rawData\\n \\ttransform: [\\n \\t// when a country is selected, filter out unrelated data\\n \\t{\\n \\ttype: filter\\n \\texpr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2\\n \\t}\\n \\t// Set new key for later lookups - identifies each node\\n \\t{type: \\"formula\\", expr: \\"datum.stk1+datum.stk2\\", as: \\"key\\"}\\n \\t// instead of each table row, create two new rows,\\n \\t// one for the source (stack=stk1) and one for destination node (stack=stk2).\\n \\t// The country code stored in stk1 and stk2 fields is placed into grpId field.\\n \\t{\\n \\ttype: fold\\n \\tfields: [\\"stk1\\", \\"stk2\\"]\\n \\tas: [\\"stack\\", \\"grpId\\"]\\n \\t}\\n \\t// Create a sortkey, different for stk1 and stk2 stacks.\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.stack == \'stk1\' ? datum.stk1+datum.stk2 : datum.stk2+datum.stk1\\n \\tas: sortField\\n \\t}\\n \\t// Calculate y0 and y1 positions for stacking nodes one on top of the other,\\n \\t// independently for each stack, and ensuring they are in the proper order,\\n \\t// alphabetical from the top (reversed on the y axis)\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"sortField\\", order: \\"descending\\"}\\n \\tfield: size\\n \\t}\\n \\t// calculate vertical center point for each node, used to draw edges\\n \\t{type: \\"formula\\", expr: \\"(datum.y0+datum.y1)/2\\", as: \\"yc\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: groups\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// combine all nodes into country groups, summing up the doc counts\\n \\t{\\n \\ttype: aggregate\\n \\tgroupby: [\\"stack\\", \\"grpId\\"]\\n \\tfields: [\\"size\\"]\\n \\tops: [\\"sum\\"]\\n \\tas: [\\"total\\"]\\n \\t}\\n \\t// re-calculate the stacking y0,y1 values\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"grpId\\", order: \\"descending\\"}\\n \\tfield: total\\n \\t}\\n \\t// project y0 and y1 values to screen coordinates\\n \\t// doing it once here instead of doing it several times in marks\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y0)\\", as: \\"scaledY0\\"}\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y1)\\", as: \\"scaledY1\\"}\\n \\t// boolean flag if the label should be on the right of the stack\\n \\t{type: \\"formula\\", expr: \\"datum.stack == \'stk1\'\\", as: \\"rightLabel\\"}\\n \\t// Calculate traffic percentage for this country using \\"y\\" scale\\n \\t// domain upper bound, which represents the total traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.total/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n\\t{\\n \\t// This is a temp lookup table with all the \'stk2\' stack nodes\\n \\tname: destinationNodes\\n \\tsource: nodes\\n \\ttransform: [\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk2\'\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: edges\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// we only want nodes from the left stack\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk1\'\\"}\\n \\t// find corresponding node from the right stack, keep it as \\"target\\"\\n \\t{\\n \\ttype: lookup\\n \\tfrom: destinationNodes\\n \\tkey: key\\n \\tfields: [\\"key\\"]\\n \\tas: [\\"target\\"]\\n \\t}\\n \\t// calculate SVG link path between stk1 and stk2 stacks for the node pair\\n \\t{\\n \\ttype: linkpath\\n \\torient: horizontal\\n \\tshape: diagonal\\n \\tsourceY: {expr: \\"scale(\'y\', datum.yc)\\"}\\n \\tsourceX: {expr: \\"scale(\'x\', \'stk1\') + bandwidth(\'x\')\\"}\\n \\ttargetY: {expr: \\"scale(\'y\', datum.target.yc)\\"}\\n \\ttargetX: {expr: \\"scale(\'x\', \'stk2\')\\"}\\n \\t}\\n \\t// A little trick to calculate the thickness of the line.\\n \\t// The value needs to be the same as the hight of the node, but scaling\\n \\t// size to screen\'s height gives inversed value because screen\'s Y\\n \\t// coordinate goes from the top to the bottom, whereas the graph\'s Y=0\\n \\t// is at the bottom. So subtracting scaled doc count from screen height\\n \\t// (which is the \\"lower\\" bound of the \\"y\\" scale) gives us the right value\\n \\t{\\n \\ttype: formula\\n \\texpr: range(\'y\')[0]-scale(\'y\', datum.size)\\n \\tas: strokeWidth\\n \\t}\\n \\t// Tooltip needs individual link\'s percentage of all traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.size/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n ]\\n scales: [\\n\\t{\\n \\t// calculates horizontal stack positioning\\n \\tname: x\\n \\ttype: band\\n \\trange: width\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n \\tpaddingOuter: 0.05\\n \\tpaddingInner: 0.95\\n\\t}\\n\\t{\\n \\t// this scale goes up as high as the highest y1 value of all nodes\\n \\tname: y\\n \\ttype: linear\\n \\trange: height\\n \\tdomain: {data: \\"nodes\\", field: \\"y1\\"}\\n\\t}\\n\\t{\\n \\t// use rawData to ensure the colors stay the same when clicking.\\n \\tname: color\\n \\ttype: ordinal\\n \\trange: category\\n \\tdomain: {data: \\"rawData\\", field: \\"stk1\\"}\\n\\t}\\n\\t{\\n \\t// this scale is used to map internal ids (stk1, stk2) to stack names\\n \\tname: stackNames\\n \\ttype: ordinal\\n \\trange: [\\"Source\\", \\"Destination\\"]\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n\\t}\\n ]\\n axes: [\\n\\t{\\n \\t// x axis should use custom label formatting to print proper stack names\\n \\torient: bottom\\n \\tscale: x\\n \\tencode: {\\n \\tlabels: {\\n \\tupdate: {\\n \\ttext: {scale: \\"stackNames\\", field: \\"value\\"}\\n \\t}\\n \\t}\\n \\t}\\n\\t}\\n\\t{orient: \\"left\\", scale: \\"y\\"}\\n ]\\n marks: [\\n\\t{\\n \\t// draw the connecting line between stacks\\n \\ttype: path\\n \\tname: edgeMark\\n \\tfrom: {data: \\"edges\\"}\\n \\t// this prevents some autosizing issues with large strokeWidth for paths\\n \\tclip: true\\n \\tencode: {\\n \\tupdate: {\\n \\t// By default use color of the left node, except when showing traffic\\n \\t// from just one country, in which case use destination color.\\n \\tstroke: [\\n \\t{\\n \\ttest: groupSelector && groupSelector.stack==\'stk1\'\\n \\tscale: color\\n \\tfield: stk2\\n \\t}\\n \\t{scale: \\"color\\", field: \\"stk1\\"}\\n \\t]\\n \\tstrokeWidth: {field: \\"strokeWidth\\"}\\n \\tpath: {field: \\"path\\"}\\n \\t// when showing all traffic, and hovering over a country,\\n \\t// highlight the traffic from that country.\\n \\tstrokeOpacity: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3\\n \\t}\\n \\t// Ensure that the hover-selected edges show on top\\n \\tzindex: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0\\n \\t}\\n \\t// format tooltip string\\n \\ttooltip: {\\n \\tsignal: datum.stk1 + \' → \' + datum.stk2 + \'\\t\' + format(datum.size, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\t// Simple mouseover highlighting of a single line\\n \\thover: {\\n \\tstrokeOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw stack groups (countries)\\n \\ttype: rect\\n \\tname: groupMark\\n \\tfrom: {data: \\"groups\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tfill: {scale: \\"color\\", field: \\"grpId\\"}\\n \\twidth: {scale: \\"x\\", band: 1}\\n \\t}\\n \\tupdate: {\\n \\tx: {scale: \\"x\\", field: \\"stack\\"}\\n \\ty: {field: \\"scaledY0\\"}\\n \\ty2: {field: \\"scaledY1\\"}\\n \\tfillOpacity: {value: 0.6}\\n \\ttooltip: {\\n \\tsignal: datum.grpId + \' \' + format(datum.total, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\thover: {\\n \\tfillOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw country code labels on the inner side of the stack\\n \\ttype: text\\n \\tfrom: {data: \\"groups\\"}\\n \\t// don\'t process events for the labels - otherwise line mouseover is unclean\\n \\tinteractive: false\\n \\tencode: {\\n \\tupdate: {\\n \\t// depending on which stack it is, position x with some padding\\n \\tx: {\\n \\tsignal: scale(\'x\', datum.stack) + (datum.rightLabel ? bandwidth(\'x\') + 8 : -8)\\n \\t}\\n \\t// middle of the group\\n \\tyc: {signal: \\"(datum.scaledY0 + datum.scaledY1)/2\\"}\\n \\talign: {signal: \\"datum.rightLabel ? \'left\' : \'right\'\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\t// only show text label if the group\'s height is large enough\\n \\ttext: {signal: \\"abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : \'\'\\"}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// Create a \\"show all\\" button. Shown only when a country is selected.\\n \\ttype: group\\n \\tdata: [\\n \\t// We need to make the button show only when groupSelector signal is true.\\n \\t// Each mark is drawn as many times as there are elements in the backing data.\\n \\t// Which means that if values list is empty, it will not be drawn.\\n \\t// Here I create a data source with one empty object, and filter that list\\n \\t// based on the signal value. This can only be done in a group.\\n \\t{\\n \\tname: dataForShowAll\\n \\tvalues: [{}]\\n \\ttransform: [{type: \\"filter\\", expr: \\"groupSelector\\"}]\\n \\t}\\n \\t]\\n \\t// Set button size and positioning\\n \\tencode: {\\n \\tenter: {\\n \\txc: {signal: \\"width/2\\"}\\n \\ty: {value: 30}\\n \\twidth: {value: 80}\\n \\theight: {value: 30}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\t// This group is shown as a button with rounded corners.\\n \\ttype: group\\n \\t// mark name allows signal capturing\\n \\tname: groupReset\\n \\t// Only shows button if dataForShowAll has values.\\n \\tfrom: {data: \\"dataForShowAll\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tcornerRadius: {value: 6}\\n \\tfill: {value: \\"#F5F7FA\\"}\\n \\tstroke: {value: \\"#c1c1c1\\"}\\n \\tstrokeWidth: {value: 2}\\n \\t// use parent group\'s size\\n \\theight: {\\n \\tfield: {group: \\"height\\"}\\n \\t}\\n \\twidth: {\\n \\tfield: {group: \\"width\\"}\\n \\t}\\n \\t}\\n \\tupdate: {\\n \\t// groups are transparent by default\\n \\topacity: {value: 1}\\n \\t}\\n \\thover: {\\n \\topacity: {value: 0.7}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\ttype: text\\n \\t// if true, it will prevent clicking on the button when over text.\\n \\tinteractive: false\\n \\tencode: {\\n \\tenter: {\\n \\t// center text in the paren group\\n \\txc: {\\n \\tfield: {group: \\"width\\"}\\n \\tmult: 0.5\\n \\t}\\n \\tyc: {\\n \\tfield: {group: \\"height\\"}\\n \\tmult: 0.5\\n \\toffset: 2\\n \\t}\\n \\talign: {value: \\"center\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\ttext: {value: \\"Show All\\"}\\n \\t}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t]\\n\\t}\\n ]\\n signals: [\\n\\t{\\n \\t// used to highlight traffic to/from the same country\\n \\tname: groupHover\\n \\tvalue: {}\\n \\ton: [\\n \\t{\\n \\tevents: @groupMark:mouseover\\n \\tupdate: \\"{stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{events: \\"mouseout\\", update: \\"{}\\"}\\n \\t]\\n\\t}\\n\\t// used to filter only the data related to the selected country\\n\\t{\\n \\tname: groupSelector\\n \\tvalue: false\\n \\ton: [\\n \\t{\\n \\t// Clicking groupMark sets this signal to the filter values\\n \\tevents: @groupMark:click!\\n \\tupdate: \\"{stack:datum.stack, stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{\\n \\t// Clicking \\"show all\\" button, or double-clicking anywhere resets it\\n \\tevents: [\\n \\t{type: \\"click\\", markname: \\"groupReset\\"}\\n \\t{type: \\"dblclick\\"}\\n \\t]\\n \\tupdate: \\"false\\"\\n \\t}\\n \\t]\\n\\t}\\n ]\\n}\\n"},"aggs":[]}', + '{"title":"[Logs] Source and Destination Sankey Chart","type":"vega","params":{"spec":"{ \\n $schema: https://vega.github.io/schema/vega/v5.json\\n data: [\\n\\t{\\n \\t// query ES based on the currently selected time range and filter string\\n \\tname: rawData\\n \\turl: {\\n \\t%context%: true\\n \\t%timefield%: timestamp\\n \\tindex: kibana_sample_data_logs\\n \\tbody: {\\n \\tsize: 0\\n \\taggs: {\\n \\ttable: {\\n \\tcomposite: {\\n \\tsize: 10000\\n \\tsources: [\\n \\t{\\n \\tstk1: {\\n \\tterms: {field: \\"geo.src\\"}\\n \\t}\\n \\t}\\n \\t{\\n \\tstk2: {\\n \\tterms: {field: \\"geo.dest\\"}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t// From the result, take just the data we are interested in\\n \\tformat: {property: \\"aggregations.table.buckets\\"}\\n \\t// Convert key.stk1 -> stk1 for simpler access below\\n \\ttransform: [\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk1\\", as: \\"stk1\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk2\\", as: \\"stk2\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.doc_count\\", as: \\"size\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: nodes\\n \\tsource: rawData\\n \\ttransform: [\\n \\t// when a country is selected, filter out unrelated data\\n \\t{\\n \\ttype: filter\\n \\texpr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2\\n \\t}\\n \\t// Set new key for later lookups - identifies each node\\n \\t{type: \\"formula\\", expr: \\"datum.stk1+datum.stk2\\", as: \\"key\\"}\\n \\t// instead of each table row, create two new rows,\\n \\t// one for the source (stack=stk1) and one for destination node (stack=stk2).\\n \\t// The country code stored in stk1 and stk2 fields is placed into grpId field.\\n \\t{\\n \\ttype: fold\\n \\tfields: [\\"stk1\\", \\"stk2\\"]\\n \\tas: [\\"stack\\", \\"grpId\\"]\\n \\t}\\n \\t// Create a sortkey, different for stk1 and stk2 stacks.\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.stack == \'stk1\' ? datum.stk1+datum.stk2 : datum.stk2+datum.stk1\\n \\tas: sortField\\n \\t}\\n \\t// Calculate y0 and y1 positions for stacking nodes one on top of the other,\\n \\t// independently for each stack, and ensuring they are in the proper order,\\n \\t// alphabetical from the top (reversed on the y axis)\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"sortField\\", order: \\"descending\\"}\\n \\tfield: size\\n \\t}\\n \\t// calculate vertical center point for each node, used to draw edges\\n \\t{type: \\"formula\\", expr: \\"(datum.y0+datum.y1)/2\\", as: \\"yc\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: groups\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// combine all nodes into country groups, summing up the doc counts\\n \\t{\\n \\ttype: aggregate\\n \\tgroupby: [\\"stack\\", \\"grpId\\"]\\n \\tfields: [\\"size\\"]\\n \\tops: [\\"sum\\"]\\n \\tas: [\\"total\\"]\\n \\t}\\n \\t// re-calculate the stacking y0,y1 values\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"grpId\\", order: \\"descending\\"}\\n \\tfield: total\\n \\t}\\n \\t// project y0 and y1 values to screen coordinates\\n \\t// doing it once here instead of doing it several times in marks\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y0)\\", as: \\"scaledY0\\"}\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y1)\\", as: \\"scaledY1\\"}\\n \\t// boolean flag if the label should be on the right of the stack\\n \\t{type: \\"formula\\", expr: \\"datum.stack == \'stk1\'\\", as: \\"rightLabel\\"}\\n \\t// Calculate traffic percentage for this country using \\"y\\" scale\\n \\t// domain upper bound, which represents the total traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.total/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n\\t{\\n \\t// This is a temp lookup table with all the \'stk2\' stack nodes\\n \\tname: destinationNodes\\n \\tsource: nodes\\n \\ttransform: [\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk2\'\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: edges\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// we only want nodes from the left stack\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk1\'\\"}\\n \\t// find corresponding node from the right stack, keep it as \\"target\\"\\n \\t{\\n \\ttype: lookup\\n \\tfrom: destinationNodes\\n \\tkey: key\\n \\tfields: [\\"key\\"]\\n \\tas: [\\"target\\"]\\n \\t}\\n \\t// calculate SVG link path between stk1 and stk2 stacks for the node pair\\n \\t{\\n \\ttype: linkpath\\n \\torient: horizontal\\n \\tshape: diagonal\\n \\tsourceY: {expr: \\"scale(\'y\', datum.yc)\\"}\\n \\tsourceX: {expr: \\"scale(\'x\', \'stk1\') + bandwidth(\'x\')\\"}\\n \\ttargetY: {expr: \\"scale(\'y\', datum.target.yc)\\"}\\n \\ttargetX: {expr: \\"scale(\'x\', \'stk2\')\\"}\\n \\t}\\n \\t// A little trick to calculate the thickness of the line.\\n \\t// The value needs to be the same as the hight of the node, but scaling\\n \\t// size to screen\'s height gives inversed value because screen\'s Y\\n \\t// coordinate goes from the top to the bottom, whereas the graph\'s Y=0\\n \\t// is at the bottom. So subtracting scaled doc count from screen height\\n \\t// (which is the \\"lower\\" bound of the \\"y\\" scale) gives us the right value\\n \\t{\\n \\ttype: formula\\n \\texpr: range(\'y\')[0]-scale(\'y\', datum.size)\\n \\tas: strokeWidth\\n \\t}\\n \\t// Tooltip needs individual link\'s percentage of all traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.size/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n ]\\n scales: [\\n\\t{\\n \\t// calculates horizontal stack positioning\\n \\tname: x\\n \\ttype: band\\n \\trange: width\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n \\tpaddingOuter: 0.05\\n \\tpaddingInner: 0.95\\n\\t}\\n\\t{\\n \\t// this scale goes up as high as the highest y1 value of all nodes\\n \\tname: y\\n \\ttype: linear\\n \\trange: height\\n \\tdomain: {data: \\"nodes\\", field: \\"y1\\"}\\n\\t}\\n\\t{\\n \\t// use rawData to ensure the colors stay the same when clicking.\\n \\tname: color\\n \\ttype: ordinal\\n \\trange: category\\n \\tdomain: {data: \\"rawData\\", field: \\"stk1\\"}\\n\\t}\\n\\t{\\n \\t// this scale is used to map internal ids (stk1, stk2) to stack names\\n \\tname: stackNames\\n \\ttype: ordinal\\n \\trange: [\\"Source\\", \\"Destination\\"]\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n\\t}\\n ]\\n axes: [\\n\\t{\\n \\t// x axis should use custom label formatting to print proper stack names\\n \\torient: bottom\\n \\tscale: x\\n \\tencode: {\\n \\tlabels: {\\n \\tupdate: {\\n \\ttext: {scale: \\"stackNames\\", field: \\"value\\"}\\n \\t}\\n \\t}\\n \\t}\\n\\t}\\n\\t{orient: \\"left\\", scale: \\"y\\"}\\n ]\\n marks: [\\n\\t{\\n \\t// draw the connecting line between stacks\\n \\ttype: path\\n \\tname: edgeMark\\n \\tfrom: {data: \\"edges\\"}\\n \\t// this prevents some autosizing issues with large strokeWidth for paths\\n \\tclip: true\\n \\tencode: {\\n \\tupdate: {\\n \\t// By default use color of the left node, except when showing traffic\\n \\t// from just one country, in which case use destination color.\\n \\tstroke: [\\n \\t{\\n \\ttest: groupSelector && groupSelector.stack==\'stk1\'\\n \\tscale: color\\n \\tfield: stk2\\n \\t}\\n \\t{scale: \\"color\\", field: \\"stk1\\"}\\n \\t]\\n \\tstrokeWidth: {field: \\"strokeWidth\\"}\\n \\tpath: {field: \\"path\\"}\\n \\t// when showing all traffic, and hovering over a country,\\n \\t// highlight the traffic from that country.\\n \\tstrokeOpacity: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3\\n \\t}\\n \\t// Ensure that the hover-selected edges show on top\\n \\tzindex: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0\\n \\t}\\n \\t// format tooltip string\\n \\ttooltip: {\\n \\tsignal: datum.stk1 + \' → \' + datum.stk2 + \'\\t\' + format(datum.size, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\t// Simple mouseover highlighting of a single line\\n \\thover: {\\n \\tstrokeOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw stack groups (countries)\\n \\ttype: rect\\n \\tname: groupMark\\n \\tfrom: {data: \\"groups\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tfill: {scale: \\"color\\", field: \\"grpId\\"}\\n \\twidth: {scale: \\"x\\", band: 1}\\n \\t}\\n \\tupdate: {\\n \\tx: {scale: \\"x\\", field: \\"stack\\"}\\n \\ty: {field: \\"scaledY0\\"}\\n \\ty2: {field: \\"scaledY1\\"}\\n \\tfillOpacity: {value: 0.6}\\n \\ttooltip: {\\n \\tsignal: datum.grpId + \' \' + format(datum.total, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\thover: {\\n \\tfillOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw country code labels on the inner side of the stack\\n \\ttype: text\\n \\tfrom: {data: \\"groups\\"}\\n \\t// don\'t process events for the labels - otherwise line mouseover is unclean\\n \\tinteractive: false\\n \\tencode: {\\n \\tupdate: {\\n \\t// depending on which stack it is, position x with some padding\\n \\tx: {\\n \\tsignal: scale(\'x\', datum.stack) + (datum.rightLabel ? bandwidth(\'x\') + 8 : -8)\\n \\t}\\n \\t// middle of the group\\n \\tyc: {signal: \\"(datum.scaledY0 + datum.scaledY1)/2\\"}\\n \\talign: {signal: \\"datum.rightLabel ? \'left\' : \'right\'\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\t// only show text label if the group\'s height is large enough\\n \\ttext: {signal: \\"abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : \'\'\\"}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// Create a \\"show all\\" button. Shown only when a country is selected.\\n \\ttype: group\\n \\tdata: [\\n \\t// We need to make the button show only when groupSelector signal is true.\\n \\t// Each mark is drawn as many times as there are elements in the backing data.\\n \\t// Which means that if values list is empty, it will not be drawn.\\n \\t// Here I create a data source with one empty object, and filter that list\\n \\t// based on the signal value. This can only be done in a group.\\n \\t{\\n \\tname: dataForShowAll\\n \\tvalues: [{}]\\n \\ttransform: [{type: \\"filter\\", expr: \\"groupSelector\\"}]\\n \\t}\\n \\t]\\n \\t// Set button size and positioning\\n \\tencode: {\\n \\tenter: {\\n \\txc: {signal: \\"width/2\\"}\\n \\ty: {value: 30}\\n \\twidth: {value: 80}\\n \\theight: {value: 30}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\t// This group is shown as a button with rounded corners.\\n \\ttype: group\\n \\t// mark name allows signal capturing\\n \\tname: groupReset\\n \\t// Only shows button if dataForShowAll has values.\\n \\tfrom: {data: \\"dataForShowAll\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tcornerRadius: {value: 6}\\n \\tfill: {value: \\"#F5F7FA\\"}\\n \\tstroke: {value: \\"#c1c1c1\\"}\\n \\tstrokeWidth: {value: 2}\\n \\t// use parent group\'s size\\n \\theight: {\\n \\tfield: {group: \\"height\\"}\\n \\t}\\n \\twidth: {\\n \\tfield: {group: \\"width\\"}\\n \\t}\\n \\t}\\n \\tupdate: {\\n \\t// groups are transparent by default\\n \\topacity: {value: 1}\\n \\t}\\n \\thover: {\\n \\topacity: {value: 0.7}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\ttype: text\\n \\t// if true, it will prevent clicking on the button when over text.\\n \\tinteractive: false\\n \\tencode: {\\n \\tenter: {\\n \\t// center text in the paren group\\n \\txc: {\\n \\tfield: {group: \\"width\\"}\\n \\tmult: 0.5\\n \\t}\\n \\tyc: {\\n \\tfield: {group: \\"height\\"}\\n \\tmult: 0.5\\n \\toffset: 2\\n \\t}\\n \\talign: {value: \\"center\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\ttext: {value: \\"Show All\\"}\\n \\t}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t]\\n\\t}\\n ]\\n signals: [\\n\\t{\\n \\t// used to highlight traffic to/from the same country\\n \\tname: groupHover\\n \\tvalue: {}\\n \\ton: [\\n \\t{\\n \\tevents: @groupMark:mouseover\\n \\tupdate: \\"{stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{events: \\"mouseout\\", update: \\"{}\\"}\\n \\t]\\n\\t}\\n\\t// used to filter only the data related to the selected country\\n\\t{\\n \\tname: groupSelector\\n \\tvalue: false\\n \\ton: [\\n \\t{\\n \\t// Clicking groupMark sets this signal to the filter values\\n \\tevents: @groupMark:click!\\n \\tupdate: \\"{stack:datum.stack, stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{\\n \\t// Clicking \\"show all\\" button, or double-clicking anywhere resets it\\n \\tevents: [\\n \\t{type: \\"click\\", markname: \\"groupReset\\"}\\n \\t{type: \\"dblclick\\"}\\n \\t]\\n \\tupdate: \\"false\\"\\n \\t}\\n \\t]\\n\\t}\\n ]\\n}\\n"},"aggs":[]}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/inspector/kibana.json b/src/plugins/inspector/kibana.json index 39d3ff65eed53..99a38d2928df6 100644 --- a/src/plugins/inspector/kibana.json +++ b/src/plugins/inspector/kibana.json @@ -2,5 +2,6 @@ "id": "inspector", "version": "kibana", "server": false, - "ui": true + "ui": true, + "extraPublicDirs": ["common", "common/adapters/request"] } diff --git a/src/plugins/kibana_legacy/kibana.json b/src/plugins/kibana_legacy/kibana.json index e96b4859a36d0..606acd8b88b05 100644 --- a/src/plugins/kibana_legacy/kibana.json +++ b/src/plugins/kibana_legacy/kibana.json @@ -2,5 +2,6 @@ "id": "kibanaLegacy", "version": "kibana", "server": true, - "ui": true + "ui": true, + "extraPublicDirs": ["common/kbn_base_url"] } diff --git a/src/plugins/kibana_utils/kibana.json b/src/plugins/kibana_utils/kibana.json index 6fa39d82d1021..7e2127c27548e 100644 --- a/src/plugins/kibana_utils/kibana.json +++ b/src/plugins/kibana_utils/kibana.json @@ -1,5 +1,10 @@ { "id": "kibanaUtils", "version": "kibana", - "ui": true + "ui": true, + "extraPublicDirs": [ + "common", + "demos/state_containers/todomvc", + "common/state_containers" + ] } diff --git a/src/plugins/maps_legacy/public/leaflet.js b/src/plugins/maps_legacy/public/leaflet.js index e36da2c52b8c5..bee75021c76ad 100644 --- a/src/plugins/maps_legacy/public/leaflet.js +++ b/src/plugins/maps_legacy/public/leaflet.js @@ -17,8 +17,6 @@ * under the License. */ -export let L; - if (!window.hasOwnProperty('L')) { require('leaflet/dist/leaflet.css'); window.L = require('leaflet/dist/leaflet.js'); @@ -31,6 +29,6 @@ if (!window.hasOwnProperty('L')) { require('leaflet-draw/dist/leaflet.draw.js'); require('leaflet-responsive-popup/leaflet.responsive.popup.css'); require('leaflet-responsive-popup/leaflet.responsive.popup.js'); -} else { - L = window.L; } + +export const L = window.L; diff --git a/src/plugins/saved_objects_management/kibana.json b/src/plugins/saved_objects_management/kibana.json index 22135ce4558ae..6184d890c415c 100644 --- a/src/plugins/saved_objects_management/kibana.json +++ b/src/plugins/saved_objects_management/kibana.json @@ -4,5 +4,6 @@ "server": true, "ui": true, "requiredPlugins": ["home", "management", "data"], - "optionalPlugins": ["dashboard", "visualizations", "discover"] + "optionalPlugins": ["dashboard", "visualizations", "discover"], + "extraPublicDirs": ["public/lib"] } diff --git a/src/plugins/saved_objects_management/server/lib/find_all.test.ts b/src/plugins/saved_objects_management/server/lib/find_all.test.ts index 2515d11f6d4bb..823a103d8bab3 100644 --- a/src/plugins/saved_objects_management/server/lib/find_all.test.ts +++ b/src/plugins/saved_objects_management/server/lib/find_all.test.ts @@ -18,17 +18,18 @@ */ import { times } from 'lodash'; -import { SavedObjectsFindOptions, SavedObject } from 'src/core/server'; +import { SavedObjectsFindOptions, SavedObjectsFindResult } from 'src/core/server'; import { savedObjectsClientMock } from '../../../../core/server/mocks'; import { findAll } from './find_all'; describe('findAll', () => { let savedObjectsClient: ReturnType; - const createObj = (id: number): SavedObject => ({ + const createObj = (id: number): SavedObjectsFindResult => ({ type: 'type', id: `id-${id}`, attributes: {}, + score: 1, references: [], }); diff --git a/src/plugins/saved_objects_management/server/lib/find_relationships.test.ts b/src/plugins/saved_objects_management/server/lib/find_relationships.test.ts index 2c8997c9af21a..e18a45d9bdf44 100644 --- a/src/plugins/saved_objects_management/server/lib/find_relationships.test.ts +++ b/src/plugins/saved_objects_management/server/lib/find_relationships.test.ts @@ -77,6 +77,7 @@ describe('findRelationships', () => { type: 'parent-type', id: 'parent-id', attributes: {}, + score: 1, references: [], }, ], diff --git a/src/plugins/telemetry/kibana.json b/src/plugins/telemetry/kibana.json index f623f4f2a565d..a497597762520 100644 --- a/src/plugins/telemetry/kibana.json +++ b/src/plugins/telemetry/kibana.json @@ -6,5 +6,8 @@ "requiredPlugins": [ "telemetryCollectionManager", "usageCollection" + ], + "extraPublicDirs": [ + "common/constants" ] } diff --git a/src/plugins/ui_actions/kibana.json b/src/plugins/ui_actions/kibana.json index 44ecbbfa68408..907cbabbdf9c9 100644 --- a/src/plugins/ui_actions/kibana.json +++ b/src/plugins/ui_actions/kibana.json @@ -2,5 +2,8 @@ "id": "uiActions", "version": "kibana", "server": false, - "ui": true + "ui": true, + "extraPublicDirs": [ + "public/tests/test_samples" + ] } diff --git a/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss index e486f56fe0fe8..9d50b0875dd0d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss +++ b/src/plugins/vis_type_timeseries/public/application/components/_color_picker.scss @@ -8,39 +8,6 @@ position: relative; } -.tvbColorPicker__swatch-empty, -.tvbColorPicker__swatch { - // SASSTODO: Replace with EUI component - // sass-lint:disable-block placeholder-in-extend - @extend .euiColorPickerSwatch; -} - -.tvbColorPicker__swatch-empty { - background-color: transparent; - background-size: 22px 22px; - background-image: repeating-linear-gradient( - -45deg, - $euiColorDanger, - $euiColorDanger 2px, - transparent 2px, - transparent $euiSize - ); -} - .tvbColorPicker__clear { margin-left: $euiSizeXS; } - -.tvbColorPicker__popover { - position: absolute; - top: $euiSizeL; - z-index: 2; -} - -.tvbColorPicker__cover { - position: fixed; - top: 0; - right: 0; - left: 0; - bottom: 0; -} diff --git a/src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss b/src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss deleted file mode 100644 index f2f5eff35fc5d..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/_custom_color_picker.scss +++ /dev/null @@ -1,97 +0,0 @@ -// EUITODO: Convert to EuiColorPicker -// with additional support for alpha, saturation, swatches - -// SASSTODO: This custom picker moved all styles from react-color inline styles -// to SASS, but it should be in EUI. -// Also, some pixel values were kept as is to match inline styles from react-color -.tvbColorPickerPopUp { - @include euiBottomShadowMedium; - background-color: $euiColorEmptyShade; - border-radius: $euiBorderRadius; - box-sizing: initial; - width: 275px; - font-family: 'Menlo'; -} - -.tvbColorPickerPopUp__saturation { - width: 100%; - padding-bottom: 55%; - position: relative; - border-radius: $euiBorderRadius $euiBorderRadius 0 0; - overflow: hidden; -} - -.tvbColorPickerPopUp__body { - padding: $euiSize; -} - -.tvbColorPickerPopUp__controls { - display: flex; -} - -.tvbColorPickerPopUp__color { - width: $euiSizeXL; - - // The color indicator doesn't work, hiding it until it does - display: none; -} - -.tvbColorPickerPopUp__color-disableAlpha { - width: $euiSizeL; -} - -.tvbColorPickerPopUp__swatch { - margin-top: 6px; - width: $euiSize; - height: $euiSize; - border-radius: $euiSizeS; - position: relative; - overflow: hidden; -} - -.tvbColorPickerPopUp__swatch-disableAlpha { - width: 10px; - height: 10px; - margin: 0; -} - -.tvbColorPickerPopUp__active { - @include euiBottomShadowMedium; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: $euiSizeS; - z-index: 2; -} - -.tvbColorPickerPopUp__toggles { - flex: 1; -} - -.tvbColorPickerPopUp__hue { - height: 10px; - position: relative; - margin-bottom: $euiSizeS; -} - -.tvbColorPickerPopUp__hue-disableAlpha { - margin-bottom: 0; -} - -.tvbColorPickerPopUp__alpha { - height: 10px; - position: relative; -} - -.tvbColorPickerPopUp__alpha-disableAlpha { - display: none; -} - -.tvbColorPickerPopUp__swatches { - display: flex; - flex-wrap: wrap; - justify-content: center; - margin-top: $euiSize; -} diff --git a/src/plugins/vis_type_timeseries/public/application/components/_index.scss b/src/plugins/vis_type_timeseries/public/application/components/_index.scss index 3a10b07c23e2b..4ee5c1863946b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/_index.scss +++ b/src/plugins/vis_type_timeseries/public/application/components/_index.scss @@ -1,7 +1,6 @@ @import './annotations_editor'; @import './color_rules'; @import './color_picker'; -@import './custom_color_picker'; @import './error'; @import './no_data'; @import './markdown_editor'; diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_picker.js b/src/plugins/vis_type_timeseries/public/application/components/color_picker.js deleted file mode 100644 index f12797518cd28..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/color_picker.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ -// The color picker is not yet accessible. - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { EuiIconTip } from '@elastic/eui'; -import { CustomColorPicker } from './custom_color_picker'; -import { i18n } from '@kbn/i18n'; - -export class ColorPicker extends Component { - constructor(props) { - super(props); - this.state = { - displayPicker: false, - color: {}, - }; - } - - handleChange = (color) => { - const { rgb } = color; - const part = {}; - part[this.props.name] = `rgba(${rgb.r},${rgb.g},${rgb.b},${rgb.a})`; - if (this.props.onChange) this.props.onChange(part); - }; - - handleClick = () => { - this.setState({ displayPicker: !this.state.displayColorPicker }); - }; - - handleClose = () => { - this.setState({ displayPicker: false }); - }; - - handleClear = () => { - const part = {}; - part[this.props.name] = null; - this.props.onChange(part); - }; - - renderSwatch() { - if (!this.props.value) { - return ( - diff --git a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx index e85605e42981c..a5bcec1501ad3 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx @@ -12,10 +12,14 @@ import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; const SETUP_INSTRUCTIONS_LABEL = i18n.translate( 'xpack.apm.setupInstructionsButtonLabel', { - defaultMessage: 'Setup Instructions', + defaultMessage: 'Setup instructions', } ); +const ADD_DATA_LABEL = i18n.translate('xpack.apm.addDataButtonLabel', { + defaultMessage: 'Add data', +}); + // renders a filled button or a link as a kibana link to setup instructions export function SetupInstructionsLink({ buttonFill = false, @@ -30,8 +34,8 @@ export function SetupInstructionsLink({ {SETUP_INSTRUCTIONS_LABEL} ) : ( - - {SETUP_INSTRUCTIONS_LABEL} + + {ADD_DATA_LABEL} )} diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js index bf6cf083e00ec..87ab81e738eb8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js @@ -105,7 +105,9 @@ export default function Legends({ return ( clickLegend(i)} + onClick={ + serie.legendClickDisabled ? undefined : () => clickLegend(i) + } disabled={seriesEnabledState[i]} text={text} color={serie.color} diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js index e1ffec3a8d97f..7e74961e57ea1 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/index.js @@ -144,7 +144,7 @@ export class InnerCustomPlot extends PureComponent { const hasValidCoordinates = flatten(series.map((s) => s.data)).some((p) => isValidCoordinateValue(p.y) ); - const noHits = !hasValidCoordinates; + const noHits = this.props.noHits || !hasValidCoordinates; const plotValues = this.getPlotValues({ visibleSeries, @@ -234,6 +234,7 @@ InnerCustomPlot.propTypes = { firstSeen: PropTypes.number, }) ), + noHits: PropTypes.bool, }; InnerCustomPlot.defaultProps = { @@ -241,6 +242,8 @@ InnerCustomPlot.defaultProps = { tickFormatX: undefined, tickFormatY: (y) => y, truncateLegends: false, + xAxisTickSizeOuter: 0, + noHits: false, }; export default makeWidthFlexible(InnerCustomPlot); diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index b7ea026f80fde..6c5a200fb6f27 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -9,7 +9,7 @@ Array [ "text": Avg. - 467.6 ms + 468 ms , }, @@ -434,7 +434,7 @@ Array [ > - 467.6 ms + 468 ms @@ -2840,7 +2840,7 @@ exports[`when response has data when dragging without releasing should display S fillOpacity="0.1" height={208} pointerEvents="none" - width={240} + width={239.9999999999999} x={320} y={16} /> @@ -3370,7 +3370,7 @@ Array [ > - 467.6 ms + 468 ms diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx new file mode 100644 index 0000000000000..7aafa9e1fdcec --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/charts/ErrorRateChart/index.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiTitle } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import mean from 'lodash.mean'; +import React, { useCallback } from 'react'; +import { useChartsSync } from '../../../../hooks/useChartsSync'; +import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { callApmApi } from '../../../../services/rest/createCallApmApi'; +import { unit } from '../../../../style/variables'; +import { asPercent } from '../../../../utils/formatters'; +// @ts-ignore +import CustomPlot from '../CustomPlot'; + +const tickFormatY = (y?: number) => { + return asPercent(y || 0, 1); +}; + +export const ErrorRateChart = () => { + const { urlParams, uiFilters } = useUrlParams(); + const syncedChartsProps = useChartsSync(); + + const { serviceName, start, end, errorGroupId } = urlParams; + const { data: errorRateData } = useFetcher(() => { + if (serviceName && start && end) { + return callApmApi({ + pathname: '/api/apm/services/{serviceName}/errors/rate', + params: { + path: { + serviceName, + }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + groupId: errorGroupId, + }, + }, + }); + } + }, [serviceName, start, end, uiFilters, errorGroupId]); + + const combinedOnHover = useCallback( + (hoverX: number) => { + return syncedChartsProps.onHover(hoverX); + }, + [syncedChartsProps] + ); + + const errorRates = errorRateData?.errorRates || []; + + return ( + <> + + + {i18n.translate('xpack.apm.errorRateChart.title', { + defaultMessage: 'Error Rate', + })} + + + rate.y))), + legendClickDisabled: true, + title: i18n.translate('xpack.apm.errorRateChart.avgLabel', { + defaultMessage: 'Avg.', + }), + type: 'linemark', + hideTooltipValue: true, + }, + { + data: errorRates, + type: 'line', + color: theme.euiColorVis7, + hideLegend: true, + title: i18n.translate('xpack.apm.errorRateChart.rateLabel', { + defaultMessage: 'Rate', + }), + }, + ]} + onHover={combinedOnHover} + tickFormatY={tickFormatY} + formatTooltipValue={({ y }: { y?: number }) => + Number.isFinite(y) ? tickFormatY(y) : 'N/A' + } + height={unit * 10} + /> + + ); +}; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js index f84b0cfeda369..7eaeecca31b36 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js +++ b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js @@ -11,8 +11,8 @@ import d3 from 'd3'; import { HistogramInner } from '../index'; import response from './response.json'; import { - asDecimal, getDurationFormatter, + asInteger, } from '../../../../../utils/formatters'; import { toJson } from '../../../../../utils/testHelpers'; import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index'; @@ -33,8 +33,8 @@ describe('Histogram', () => { transactionId="myTransactionId" onClick={onClick} formatX={(time) => timeFormatter(time).formatted} - formatYShort={(t) => `${asDecimal(t)} occ.`} - formatYLong={(t) => `${asDecimal(t)} occurrences`} + formatYShort={(t) => `${asInteger(t)} occ.`} + formatYLong={(t) => `${asInteger(t)} occurrences`} tooltipHeader={(bucket) => { const xFormatted = timeFormatter(bucket.x); const x0Formatted = timeFormatter(bucket.x0); @@ -78,9 +78,9 @@ describe('Histogram', () => { const tooltips = wrapper.find('Tooltip'); expect(tooltips.length).toBe(1); - expect(tooltips.prop('header')).toBe('811.1 - 926.9 ms'); + expect(tooltips.prop('header')).toBe('811 - 927 ms'); expect(tooltips.prop('tooltipPoints')).toEqual([ - { value: '49.0 occurrences' }, + { value: '49 occurrences' }, ]); expect(tooltips.prop('x')).toEqual(869010); expect(tooltips.prop('y')).toEqual(27.5); diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap index 700602eb56929..a31b9735628ab 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap @@ -114,7 +114,7 @@ exports[`Histogram Initially should have default markup 1`] = ` x1={0} x2={0} y1={-0} - y2={10} + y2={0} /> - 0.0 ms + 0 ms - 500.0 ms + 500 ms - 1,000.0 ms + 1,000 ms - 1,500.0 ms + 1,500 ms - 2,000.0 ms + 2,000 ms - 2,500.0 ms + 2,500 ms - 3,000.0 ms + 3,000 ms @@ -371,7 +371,7 @@ exports[`Histogram Initially should have default markup 1`] = ` textAnchor="end" transform="translate(-8, 0)" > - 0.0 occ. + 0 occ. - 27.5 occ. + 28 occ. - 55.0 occ. + 55 occ. @@ -522,7 +522,7 @@ exports[`Histogram Initially should have default markup 1`] = ` y={112} /> - 811.1 - 926.9 ms + 811 - 927 ms @@ -1488,7 +1488,7 @@ exports[`Histogram when hovering over a non-empty bucket should have correct mar
- 49.0 occurrences + 49 occurrences
diff --git a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js index 4eca1a37c51bc..002ff19d0d1df 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js +++ b/x-pack/plugins/apm/public/components/shared/charts/Histogram/index.js @@ -26,6 +26,10 @@ import Tooltip from '../Tooltip'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { tint } from 'polished'; import { getTimeTicksTZ, getDomainTZ } from '../helper/timezone'; +import Legends from '../CustomPlot/Legends'; +import StatusText from '../CustomPlot/StatusText'; +import { i18n } from '@kbn/i18n'; +import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; const XY_HEIGHT = unit * 10; const XY_MARGIN = { @@ -99,6 +103,7 @@ export class HistogramInner extends PureComponent { tooltipHeader, verticalLineHover, width: XY_WIDTH, + legends, } = this.props; const { hoveredBucket } = this.state; if (isEmpty(buckets) || XY_WIDTH === 0) { @@ -139,102 +144,140 @@ export class HistogramInner extends PureComponent { const showVerticalLineHover = verticalLineHover(hoveredBucket); const showBackgroundHover = backgroundHover(hoveredBucket); + const hasValidCoordinates = buckets.some((bucket) => + isValidCoordinateValue(bucket.y) + ); + const noHits = this.props.noHits || !hasValidCoordinates; + + const xyPlotProps = { + dontCheckIfEmpty: true, + xType: this.props.xType, + width: XY_WIDTH, + height: XY_HEIGHT, + margin: XY_MARGIN, + xDomain: xDomain, + yDomain: yDomain, + }; + + const xAxisProps = { + style: { strokeWidth: '1px' }, + marginRight: 10, + tickSize: 0, + tickTotal: X_TICK_TOTAL, + tickFormat: formatX, + tickValues: xTickValues, + }; + + const emptyStateChart = ( + + + + + ); + return (
- - - - - - {showBackgroundHover && ( - - )} - - {shouldShowTooltip && ( - - )} - - {selectedBucket && ( - - )} - - - - {showVerticalLineHover && ( - - )} - - { - return { - ...bucket, - xCenter: (bucket.x0 + bucket.x) / 2, - }; - })} - onClick={this.onClick} - onHover={this.onHover} - onBlur={this.onBlur} - x={(d) => x(d.xCenter)} - y={() => 1} - /> - + {noHits ? ( + <>{emptyStateChart} + ) : ( + <> + + + + + + {showBackgroundHover && ( + + )} + + {shouldShowTooltip && ( + + )} + + {selectedBucket && ( + + )} + + + + {showVerticalLineHover && hoveredBucket?.x && ( + + )} + + { + return { + ...bucket, + xCenter: (bucket.x0 + bucket.x) / 2, + }; + })} + onClick={this.onClick} + onHover={this.onHover} + onBlur={this.onBlur} + x={(d) => x(d.xCenter)} + y={() => 1} + /> + + + {legends && ( + {}} + truncateLegends={false} + noHits={noHits} + /> + )} + + )}
); @@ -255,6 +298,8 @@ HistogramInner.propTypes = { verticalLineHover: PropTypes.func, width: PropTypes.number.isRequired, xType: PropTypes.string, + legends: PropTypes.array, + noHits: PropTypes.bool, }; HistogramInner.defaultProps = { @@ -265,6 +310,7 @@ HistogramInner.defaultProps = { tooltipHeader: () => null, verticalLineHover: () => null, xType: 'linear', + noHits: false, }; export default makeWidthFlexible(HistogramInner); diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx index 2b6f0c7aa1319..7dc68910c6bed 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx @@ -37,7 +37,7 @@ describe('ErrorMarker', () => { act(() => { fireEvent.click(component.getByTestId('popover')); }); - expectTextsInDocument(component, ['10.0 ms']); + expectTextsInDocument(component, ['10 ms']); return component; } function getKueryDecoded(url: string) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 915b55f29ef80..4821e06419e34 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -24,10 +24,10 @@ import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { - asInteger, tpmUnit, TimeFormatter, getDurationFormatter, + asDecimal, } from '../../../../utils/formatters'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; import { LicenseContext } from '../../../../context/LicenseContext'; @@ -86,7 +86,7 @@ export class TransactionCharts extends Component { public getTPMFormatter = (t: number) => { const { urlParams } = this.props; const unit = tpmUnit(urlParams.transactionType); - return `${asInteger(t)} ${unit}`; + return `${asDecimal(t)} ${unit}`; }; public getTPMTooltipFormatter = (p: Coordinate) => { diff --git a/x-pack/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts b/x-pack/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts index 2f0a30a5019a9..901e6052bbf06 100644 --- a/x-pack/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts +++ b/x-pack/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts @@ -15,6 +15,8 @@ import { warningColor, errorColor, } from '../../utils/httpStatusCodeToColor'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ApmTimeSeriesResponse } from '../../../server/lib/transactions/charts/get_timeseries_data/transform'; describe('chartSelectors', () => { describe('getAnomalyScoreSeries', () => { @@ -98,7 +100,7 @@ describe('chartSelectors', () => { }); describe('getTpmSeries', () => { - const apmTimeseries = { + const apmTimeseries: ApmTimeSeriesResponse = { responseTimes: { avg: [], p95: [], @@ -107,13 +109,14 @@ describe('chartSelectors', () => { tpmBuckets: [ { key: 'HTTP 2xx', + avg: 3.5, dataPoints: [ { x: 0, y: 5 }, - { x: 0, y: 2 }, + { x: 1, y: 2 }, ], }, - { key: 'HTTP 4xx', dataPoints: [{ x: 0, y: 1 }] }, - { key: 'HTTP 5xx', dataPoints: [{ x: 0, y: 0 }] }, + { key: 'HTTP 4xx', avg: 1, dataPoints: [{ x: 0, y: 1 }] }, + { key: 'HTTP 5xx', avg: 0, dataPoints: [{ x: 0, y: 0 }] }, ], overallAvgDuration: 200, }; @@ -125,7 +128,7 @@ describe('chartSelectors', () => { color: successColor, data: [ { x: 0, y: 5 }, - { x: 0, y: 2 }, + { x: 1, y: 2 }, ], legendValue: '3.5 tpm', title: 'HTTP 2xx', @@ -154,7 +157,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorSecondary); }); @@ -166,7 +169,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorSecondary); }); @@ -178,7 +181,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorSecondary); }); @@ -190,7 +193,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorSecondary); }); @@ -202,7 +205,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorDanger); }); @@ -214,7 +217,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorDanger); }); @@ -226,7 +229,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorDanger); }); @@ -238,7 +241,7 @@ describe('chartSelectors', () => { expect( getTpmSeries({ ...apmTimeseries, - tpmBuckets: [{ key, dataPoints: [{ x: 0, y: 0 }] }], + tpmBuckets: [{ key, avg: 0, dataPoints: [{ x: 0, y: 0 }] }], })[0].color ).toEqual(theme.euiColorDanger); }); diff --git a/x-pack/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/plugins/apm/public/selectors/chartSelectors.ts index f8aed9dcf6d9f..714d62a703f51 100644 --- a/x-pack/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/plugins/apm/public/selectors/chartSelectors.ts @@ -7,7 +7,6 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { difference, zipObject } from 'lodash'; -import mean from 'lodash.mean'; import { rgba } from 'polished'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TimeSeriesAPIResponse } from '../../server/lib/transactions/charts'; @@ -72,7 +71,6 @@ export function getResponseTimeSeries({ }: TimeSeriesAPIResponse) { const { overallAvgDuration } = apmTimeseries; const { avg, p95, p99 } = apmTimeseries.responseTimes; - const formattedDuration = asDuration(overallAvgDuration); const series: TimeSeries[] = [ { @@ -80,7 +78,7 @@ export function getResponseTimeSeries({ defaultMessage: 'Avg.', }), data: avg, - legendValue: formattedDuration, + legendValue: asDuration(overallAvgDuration), type: 'linemark', color: theme.euiColorVis1, }, @@ -171,11 +169,10 @@ export function getTpmSeries( } return tpmBuckets.map((bucket) => { - const average = mean(bucket.dataPoints.map((p) => p.y)); return { title: bucket.key, data: bucket.dataPoints, - legendValue: `${asDecimal(average)} ${tpmUnit(transactionType || '')}`, + legendValue: `${asDecimal(bucket.avg)} ${tpmUnit(transactionType || '')}`, type: 'linemark', color: getColor(bucket.key), }; diff --git a/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts b/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts index 6d4b65d2aa9b4..c4d59beb4b7a2 100644 --- a/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts +++ b/x-pack/plugins/apm/public/utils/formatters/__test__/duration.test.ts @@ -14,14 +14,14 @@ describe('duration formatters', () => { expect(asDuration(1)).toEqual('1 μs'); expect(asDuration(toMicroseconds(1, 'milliseconds'))).toEqual('1,000 μs'); expect(asDuration(toMicroseconds(1000, 'milliseconds'))).toEqual( - '1,000.0 ms' + '1,000 ms' ); expect(asDuration(toMicroseconds(10000, 'milliseconds'))).toEqual( - '10,000.0 ms' + '10,000 ms' ); - expect(asDuration(toMicroseconds(20, 'seconds'))).toEqual('20.0 s'); - expect(asDuration(toMicroseconds(10, 'minutes'))).toEqual('10.0 min'); - expect(asDuration(toMicroseconds(1, 'hours'))).toEqual('60.0 min'); + expect(asDuration(toMicroseconds(20, 'seconds'))).toEqual('20 s'); + expect(asDuration(toMicroseconds(10, 'minutes'))).toEqual('10 min'); + expect(asDuration(toMicroseconds(1, 'hours'))).toEqual('60 min'); expect(asDuration(toMicroseconds(1.5, 'hours'))).toEqual('1.5 h'); }); @@ -41,7 +41,7 @@ describe('duration formatters', () => { describe('asMilliseconds', () => { it('converts to formatted decimal milliseconds', () => { - expect(asMillisecondDuration(0)).toEqual('0.0 ms'); + expect(asMillisecondDuration(0)).toEqual('0 ms'); }); }); }); diff --git a/x-pack/plugins/apm/public/utils/formatters/duration.ts b/x-pack/plugins/apm/public/utils/formatters/duration.ts index a603faab37538..64a9e3b952b98 100644 --- a/x-pack/plugins/apm/public/utils/formatters/duration.ts +++ b/x-pack/plugins/apm/public/utils/formatters/duration.ts @@ -18,13 +18,6 @@ interface FormatterOptions { type DurationTimeUnit = TimeUnit | 'microseconds'; -interface DurationUnit { - [unit: string]: { - label: string; - convert: (value: number) => string; - }; -} - interface ConvertedDuration { value: string; unit?: string; @@ -38,42 +31,69 @@ export type TimeFormatter = ( type TimeFormatterBuilder = (max: number) => TimeFormatter; -const durationUnit: DurationUnit = { - hours: { - label: i18n.translate('xpack.apm.formatters.hoursTimeUnitLabel', { - defaultMessage: 'h', - }), - convert: (value: number) => - asDecimal(moment.duration(value / 1000).asHours()), - }, - minutes: { - label: i18n.translate('xpack.apm.formatters.minutesTimeUnitLabel', { - defaultMessage: 'min', - }), - convert: (value: number) => - asDecimal(moment.duration(value / 1000).asMinutes()), - }, - seconds: { - label: i18n.translate('xpack.apm.formatters.secondsTimeUnitLabel', { - defaultMessage: 's', - }), - convert: (value: number) => - asDecimal(moment.duration(value / 1000).asSeconds()), - }, - milliseconds: { - label: i18n.translate('xpack.apm.formatters.millisTimeUnitLabel', { - defaultMessage: 'ms', - }), - convert: (value: number) => - asDecimal(moment.duration(value / 1000).asMilliseconds()), - }, - microseconds: { - label: i18n.translate('xpack.apm.formatters.microsTimeUnitLabel', { - defaultMessage: 'μs', - }), - convert: (value: number) => asInteger(value), - }, -}; +function asDecimalOrInteger(value: number) { + // exact 0 or above 10 should not have decimal + if (value === 0 || value >= 10) { + return asInteger(value); + } + return asDecimal(value); +} + +function getUnitLabelAndConvertedValue( + unitKey: DurationTimeUnit, + value: number +) { + switch (unitKey) { + case 'hours': { + return { + unitLabel: i18n.translate('xpack.apm.formatters.hoursTimeUnitLabel', { + defaultMessage: 'h', + }), + convertedValue: asDecimalOrInteger( + moment.duration(value / 1000).asHours() + ), + }; + } + case 'minutes': { + return { + unitLabel: i18n.translate('xpack.apm.formatters.minutesTimeUnitLabel', { + defaultMessage: 'min', + }), + convertedValue: asDecimalOrInteger( + moment.duration(value / 1000).asMinutes() + ), + }; + } + case 'seconds': { + return { + unitLabel: i18n.translate('xpack.apm.formatters.secondsTimeUnitLabel', { + defaultMessage: 's', + }), + convertedValue: asDecimalOrInteger( + moment.duration(value / 1000).asSeconds() + ), + }; + } + case 'milliseconds': { + return { + unitLabel: i18n.translate('xpack.apm.formatters.millisTimeUnitLabel', { + defaultMessage: 'ms', + }), + convertedValue: asDecimalOrInteger( + moment.duration(value / 1000).asMilliseconds() + ), + }; + } + case 'microseconds': { + return { + unitLabel: i18n.translate('xpack.apm.formatters.microsTimeUnitLabel', { + defaultMessage: 'μs', + }), + convertedValue: asInteger(value), + }; + } + } +} /** * Converts a microseconds value into the unit defined. @@ -87,16 +107,19 @@ function convertTo({ microseconds: Maybe; defaultValue?: string; }): ConvertedDuration { - const duration = durationUnit[unit]; - if (!duration || microseconds == null) { + if (microseconds == null) { return { value: defaultValue, formatted: defaultValue }; } - const convertedValue = duration.convert(microseconds); + const { convertedValue, unitLabel } = getUnitLabelAndConvertedValue( + unit, + microseconds + ); + return { value: convertedValue, - unit: duration.label, - formatted: `${convertedValue} ${duration.label}`, + unit: unitLabel, + formatted: `${convertedValue} ${unitLabel}`, }; } diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index 60a7be9391eea..80f722bae0868 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -70,3 +70,6 @@ export const APM_FEATURE = { }, }, }; + +export const APM_SERVICE_MAPS_FEATURE_NAME = 'APM service maps'; +export const APM_SERVICE_MAPS_LICENSE_TYPE = 'platinum'; diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts new file mode 100644 index 0000000000000..d558e3942a42b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/errors/get_error_rate.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + ERROR_GROUP_ID, + PROCESSOR_EVENT, + SERVICE_NAME, +} from '../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../common/processor_event'; +import { getMetricsDateHistogramParams } from '../helpers/metrics'; +import { rangeFilter } from '../helpers/range_filter'; +import { + Setup, + SetupTimeRange, + SetupUIFilters, +} from '../helpers/setup_request'; + +export async function getErrorRate({ + serviceName, + groupId, + setup, +}: { + serviceName: string; + groupId?: string; + setup: Setup & SetupTimeRange & SetupUIFilters; +}) { + const { start, end, uiFiltersES, client, indices } = setup; + + const filter = [ + { term: { [SERVICE_NAME]: serviceName } }, + { range: rangeFilter(start, end) }, + ...uiFiltersES, + ]; + + const aggs = { + response_times: { + date_histogram: getMetricsDateHistogramParams(start, end), + }, + }; + + const getTransactionBucketAggregation = async () => { + const resp = await client.search({ + index: indices['apm_oss.transactionIndices'], + body: { + size: 0, + query: { + bool: { + filter: [ + ...filter, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ], + }, + }, + aggs, + }, + }); + return { + totalHits: resp.hits.total.value, + responseTimeBuckets: resp.aggregations?.response_times.buckets, + }; + }; + const getErrorBucketAggregation = async () => { + const groupIdFilter = groupId + ? [{ term: { [ERROR_GROUP_ID]: groupId } }] + : []; + const resp = await client.search({ + index: indices['apm_oss.errorIndices'], + body: { + size: 0, + query: { + bool: { + filter: [ + ...filter, + ...groupIdFilter, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.error } }, + ], + }, + }, + aggs, + }, + }); + return resp.aggregations?.response_times.buckets; + }; + + const [transactions, errorResponseTimeBuckets] = await Promise.all([ + getTransactionBucketAggregation(), + getErrorBucketAggregation(), + ]); + + const transactionCountByTimestamp: Record = {}; + if (transactions?.responseTimeBuckets) { + transactions.responseTimeBuckets.forEach((bucket) => { + transactionCountByTimestamp[bucket.key] = bucket.doc_count; + }); + } + + const errorRates = errorResponseTimeBuckets?.map((bucket) => { + const { key, doc_count: errorCount } = bucket; + const relativeRate = errorCount / transactionCountByTimestamp[key]; + return { x: key, y: relativeRate }; + }); + + return { + noHits: transactions?.totalHits === 0, + errorRates, + }; +} diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index ac76464e2f2e8..900141e9040ae 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -8,12 +8,33 @@ import { leftJoin } from '../../../common/utils/left_join'; import { Job as AnomalyDetectionJob } from '../../../../ml/server'; import { PromiseReturnType } from '../../../typings/common'; import { IEnvOptions } from './get_service_map'; +import { Setup } from '../helpers/setup_request'; import { APM_ML_JOB_GROUP_NAME, encodeForMlApi, } from '../../../common/ml_job_constants'; +async function getApmAnomalyDetectionJobs( + setup: Setup +): Promise { + const { ml } = setup; + + if (!ml) { + return []; + } + try { + const { jobs } = await ml.anomalyDetectors.jobs(APM_ML_JOB_GROUP_NAME); + return jobs; + } catch (error) { + if (error.statusCode === 404) { + return []; + } + throw error; + } +} + type ApmMlJobCategory = NonNullable>; + export const getApmMlJobCategory = ( mlJob: AnomalyDetectionJob, serviceNames: string[] @@ -62,7 +83,10 @@ export async function getServiceAnomalies( return []; } - const { jobs: apmMlJobs } = await ml.anomalyDetectors.jobs('apm'); + const apmMlJobs = await getApmAnomalyDetectionJobs(options.setup); + if (apmMlJobs.length === 0) { + return []; + } const apmMlJobCategories = apmMlJobs .map((job) => getApmMlJobCategory(job, serviceNames)) .filter( diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap index 3ad87c48b013d..cf3fdac221b59 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap @@ -38,6 +38,11 @@ Array [ "query": Object { "bool": Object { "filter": Array [ + Object { + "term": Object { + "job_id": "myservicename-mytransactiontype-high_mean_response_time", + }, + }, Object { "exists": Object { "field": "bucket_span", @@ -57,7 +62,6 @@ Array [ }, "size": 0, }, - "index": ".ml-anomalies-myservicename-mytransactiontype-high_mean_response_time", }, ], ] diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts index 7f0e217ef4c47..313cf818a322d 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts @@ -19,7 +19,11 @@ describe('anomalyAggsFetcher', () => { intervalString: 'myInterval', mlBucketSize: 10, setup: { - client: { search: clientSpy }, + ml: { + mlSystem: { + mlAnomalySearch: clientSpy, + }, + } as any, start: 100000, end: 200000, } as any, @@ -42,7 +46,13 @@ describe('anomalyAggsFetcher', () => { return expect( anomalySeriesFetcher({ - setup: { client: { search: failedRequestSpy } }, + setup: { + ml: { + mlSystem: { + mlAnomalySearch: failedRequestSpy, + }, + } as any, + }, } as any) ).resolves.toEqual(undefined); }); @@ -53,7 +63,13 @@ describe('anomalyAggsFetcher', () => { return expect( anomalySeriesFetcher({ - setup: { client: { search: failedRequestSpy } }, + setup: { + ml: { + mlSystem: { + mlAnomalySearch: failedRequestSpy, + }, + } as any, + }, } as any) ).rejects.toThrow(otherError); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts index 365adae630297..8ee078de7f3ce 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getMlIndex } from '../../../../../common/ml_job_constants'; +import { getMlJobId } from '../../../../../common/ml_job_constants'; import { PromiseReturnType } from '../../../../../../observability/typings/common'; import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; @@ -26,19 +26,23 @@ export async function anomalySeriesFetcher({ mlBucketSize: number; setup: Setup & SetupTimeRange; }) { - const { client, start, end } = setup; + const { ml, start, end } = setup; + if (!ml) { + return; + } // move the start back with one bucket size, to ensure to get anomaly data in the beginning // this is required because ML has a minimum bucket size (default is 900s) so if our buckets are smaller, we might have several null buckets in the beginning const newStart = start - mlBucketSize * 1000; + const jobId = getMlJobId(serviceName, transactionType); const params = { - index: getMlIndex(serviceName, transactionType), body: { size: 0, query: { bool: { filter: [ + { term: { job_id: jobId } }, { exists: { field: 'bucket_span' } }, { range: { @@ -74,7 +78,7 @@ export async function anomalySeriesFetcher({ }; try { - const response = await client.search(params); + const response = await ml.mlSystem.mlAnomalySearch(params); return response; } catch (err) { const isHttpError = 'statusCode' in err; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index 31197639dc0e2..d649bfb192739 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getMlIndex } from '../../../../../common/ml_job_constants'; +import { getMlJobId } from '../../../../../common/ml_job_constants'; import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; interface IOptions { @@ -22,15 +22,20 @@ export async function getMlBucketSize({ transactionType, setup, }: IOptions): Promise { - const { client, start, end } = setup; + const { ml, start, end } = setup; + if (!ml) { + return 0; + } + const jobId = getMlJobId(serviceName, transactionType); + const params = { - index: getMlIndex(serviceName, transactionType), body: { _source: 'bucket_span', size: 1, query: { bool: { filter: [ + { term: { job_id: jobId } }, { exists: { field: 'bucket_span' } }, { range: { @@ -48,7 +53,7 @@ export async function getMlBucketSize({ }; try { - const resp = await client.search(params); + const resp = await ml.mlSystem.mlAnomalySearch(params); return resp.hits.hits[0]?._source.bucket_span || 0; } catch (err) { const isHttpError = 'statusCode' in err; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts index c8fa5adf19435..fb87f1b5707d1 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts @@ -26,8 +26,8 @@ describe('getAnomalySeries', () => { setup: { start: 0, end: 500000, - client: { search: clientSpy } as any, - internalClient: { search: clientSpy } as any, + client: { search: () => {} } as any, + internalClient: { search: () => {} } as any, config: new Proxy( {}, { @@ -46,6 +46,12 @@ describe('getAnomalySeries', () => { apmCustomLinkIndex: 'myIndex', }, dynamicIndexPattern: null as any, + ml: { + mlSystem: { + mlAnomalySearch: clientSpy, + mlCapabilities: async () => ({ isPlatinumOrTrialLicense: true }), + }, + } as any, }, }); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts index 3b1a6184c7071..6f44cfa1df9f0 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts @@ -42,6 +42,17 @@ export async function getAnomalySeries({ return; } + // don't fetch anomalies if the ML plugin is not setup + if (!setup.ml) { + return; + } + + // don't fetch anomalies if required license is not satisfied + const mlCapabilities = await setup.ml.mlSystem.mlCapabilities(); + if (!mlCapabilities.isPlatinumOrTrialLicense) { + return; + } + const mlBucketSize = await getMlBucketSize({ serviceName, transactionType, diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap index e99c32d933b0b..fc9edb496784f 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap @@ -985,326 +985,327 @@ Object { }, "tpmBuckets": Array [ Object { + "avg": 112708, "dataPoints": Array [ Object { "x": 1528113600000, - "y": 82230, + "y": 8223, }, Object { "x": 1528124400000, - "y": 81460, + "y": 8146, }, Object { "x": 1528135200000, - "y": 82320, + "y": 8232, }, Object { "x": 1528146000000, - "y": 82485, + "y": 8248.5, }, Object { "x": 1528156800000, - "y": 83995, + "y": 8399.5, }, Object { "x": 1528167600000, - "y": 82805, + "y": 8280.5, }, Object { "x": 1528178400000, - "y": 82155, + "y": 8215.5, }, Object { "x": 1528189200000, - "y": 81915, + "y": 8191.5, }, Object { "x": 1528200000000, - "y": 81475, + "y": 8147.5, }, Object { "x": 1528210800000, - "y": 83510, + "y": 8351, }, Object { "x": 1528221600000, - "y": 82345, + "y": 8234.5, }, Object { "x": 1528232400000, - "y": 82330, + "y": 8233, }, Object { "x": 1528243200000, - "y": 82755, + "y": 8275.5, }, Object { "x": 1528254000000, - "y": 83375, + "y": 8337.5, }, Object { "x": 1528264800000, - "y": 82050, + "y": 8205, }, Object { "x": 1528275600000, - "y": 81235, + "y": 8123.5, }, Object { "x": 1528286400000, - "y": 75725, + "y": 7572.5, }, Object { "x": 1528297200000, - "y": 80890, + "y": 8089, }, Object { "x": 1528308000000, - "y": 82650, + "y": 8265, }, Object { "x": 1528318800000, - "y": 81055, + "y": 8105.5, }, Object { "x": 1528329600000, - "y": 82265, + "y": 8226.5, }, Object { "x": 1528340400000, - "y": 82515, + "y": 8251.5, }, Object { "x": 1528351200000, - "y": 83020, + "y": 8302, }, Object { "x": 1528362000000, - "y": 82610, + "y": 8261, }, Object { "x": 1528372800000, - "y": 80820, + "y": 8082, }, Object { "x": 1528383600000, - "y": 82600, + "y": 8260, }, Object { "x": 1528394400000, - "y": 82670, + "y": 8267, }, Object { "x": 1528405200000, - "y": 81555, + "y": 8155.5, }, Object { "x": 1528416000000, - "y": 83350, + "y": 8335, }, Object { "x": 1528426800000, - "y": 80960, + "y": 8096, }, Object { "x": 1528437600000, - "y": 82895, + "y": 8289.5, }, Object { "x": 1528448400000, - "y": 81650, + "y": 8165, }, Object { "x": 1528459200000, - "y": 82825, + "y": 8282.5, }, Object { "x": 1528470000000, - "y": 82715, + "y": 8271.5, }, Object { "x": 1528480800000, - "y": 82460, + "y": 8246, }, Object { "x": 1528491600000, - "y": 82020, + "y": 8202, }, Object { "x": 1528502400000, - "y": 22640, + "y": 2264, }, Object { "x": 1528513200000, - "y": 22785, + "y": 2278.5, }, Object { "x": 1528524000000, - "y": 22830, + "y": 2283, }, Object { "x": 1528534800000, - "y": 22930, + "y": 2293, }, Object { "x": 1528545600000, - "y": 23360, + "y": 2336, }, Object { "x": 1528556400000, - "y": 23425, + "y": 2342.5, }, Object { "x": 1528567200000, - "y": 22605, + "y": 2260.5, }, Object { "x": 1528578000000, - "y": 23060, + "y": 2306, }, Object { "x": 1528588800000, - "y": 22675, + "y": 2267.5, }, Object { "x": 1528599600000, - "y": 23030, + "y": 2303, }, Object { "x": 1528610400000, - "y": 23070, + "y": 2307, }, Object { "x": 1528621200000, - "y": 22535, + "y": 2253.5, }, Object { "x": 1528632000000, - "y": 23055, + "y": 2305.5, }, Object { "x": 1528642800000, - "y": 22935, + "y": 2293.5, }, Object { "x": 1528653600000, - "y": 22910, + "y": 2291, }, Object { "x": 1528664400000, - "y": 23075, + "y": 2307.5, }, Object { "x": 1528675200000, - "y": 81255, + "y": 8125.5, }, Object { "x": 1528686000000, - "y": 84125, + "y": 8412.5, }, Object { "x": 1528696800000, - "y": 81440, + "y": 8144, }, Object { "x": 1528707600000, - "y": 82460, + "y": 8246, }, Object { "x": 1528718400000, - "y": 82170, + "y": 8217, }, Object { "x": 1528729200000, - "y": 85015, + "y": 8501.5, }, Object { "x": 1528740000000, - "y": 81820, + "y": 8182, }, Object { "x": 1528750800000, - "y": 83225, + "y": 8322.5, }, Object { "x": 1528761600000, - "y": 83475, + "y": 8347.5, }, Object { "x": 1528772400000, - "y": 82490, + "y": 8249, }, Object { "x": 1528783200000, - "y": 82940, + "y": 8294, }, Object { "x": 1528794000000, - "y": 83425, + "y": 8342.5, }, Object { "x": 1528804800000, - "y": 81805, + "y": 8180.5, }, Object { "x": 1528815600000, - "y": 83290, + "y": 8329, }, Object { "x": 1528826400000, - "y": 82535, + "y": 8253.5, }, Object { "x": 1528837200000, - "y": 82090, + "y": 8209, }, Object { "x": 1528848000000, - "y": 82385, + "y": 8238.5, }, Object { "x": 1528858800000, - "y": 83775, + "y": 8377.5, }, Object { "x": 1528869600000, - "y": 82970, + "y": 8297, }, Object { "x": 1528880400000, - "y": 84060, + "y": 8406, }, Object { "x": 1528891200000, - "y": 84315, + "y": 8431.5, }, Object { "x": 1528902000000, - "y": 83275, + "y": 8327.5, }, Object { "x": 1528912800000, - "y": 83615, + "y": 8361.5, }, Object { "x": 1528923600000, - "y": 82885, + "y": 8288.5, }, Object { "x": 1528934400000, - "y": 75625, + "y": 7562.5, }, Object { "x": 1528945200000, - "y": 82160, + "y": 8216, }, Object { "x": 1528956000000, - "y": 82320, + "y": 8232, }, Object { "x": 1528966800000, - "y": 81845, + "y": 8184.5, }, Object { "x": 1528977600000, @@ -1314,6 +1315,7 @@ Object { "key": "HTTP 2xx", }, Object { + "avg": 665, "dataPoints": Array [ Object { "x": 1528113600000, @@ -1381,11 +1383,11 @@ Object { }, Object { "x": 1528286400000, - "y": 20205, + "y": 2020.5, }, Object { "x": 1528297200000, - "y": 2270, + "y": 227, }, Object { "x": 1528308000000, @@ -1621,7 +1623,7 @@ Object { }, Object { "x": 1528934400000, - "y": 10775, + "y": 1077.5, }, Object { "x": 1528945200000, @@ -1643,326 +1645,327 @@ Object { "key": "HTTP 3xx", }, Object { + "avg": 8190.7, "dataPoints": Array [ Object { "x": 1528113600000, - "y": 5930, + "y": 593, }, Object { "x": 1528124400000, - "y": 6065, + "y": 606.5, }, Object { "x": 1528135200000, - "y": 6025, + "y": 602.5, }, Object { "x": 1528146000000, - "y": 5810, + "y": 581, }, Object { "x": 1528156800000, - "y": 6190, + "y": 619, }, Object { "x": 1528167600000, - "y": 5955, + "y": 595.5, }, Object { "x": 1528178400000, - "y": 6370, + "y": 637, }, Object { "x": 1528189200000, - "y": 6170, + "y": 617, }, Object { "x": 1528200000000, - "y": 5820, + "y": 582, }, Object { "x": 1528210800000, - "y": 6165, + "y": 616.5, }, Object { "x": 1528221600000, - "y": 6115, + "y": 611.5, }, Object { "x": 1528232400000, - "y": 6080, + "y": 608, }, Object { "x": 1528243200000, - "y": 6000, + "y": 600, }, Object { "x": 1528254000000, - "y": 6185, + "y": 618.5, }, Object { "x": 1528264800000, - "y": 6155, + "y": 615.5, }, Object { "x": 1528275600000, - "y": 5910, + "y": 591, }, Object { "x": 1528286400000, - "y": 5625, + "y": 562.5, }, Object { "x": 1528297200000, - "y": 6215, + "y": 621.5, }, Object { "x": 1528308000000, - "y": 6235, + "y": 623.5, }, Object { "x": 1528318800000, - "y": 5815, + "y": 581.5, }, Object { "x": 1528329600000, - "y": 6100, + "y": 610, }, Object { "x": 1528340400000, - "y": 6010, + "y": 601, }, Object { "x": 1528351200000, - "y": 5960, + "y": 596, }, Object { "x": 1528362000000, - "y": 6240, + "y": 624, }, Object { "x": 1528372800000, - "y": 5945, + "y": 594.5, }, Object { "x": 1528383600000, - "y": 6150, + "y": 615, }, Object { "x": 1528394400000, - "y": 6030, + "y": 603, }, Object { "x": 1528405200000, - "y": 5950, + "y": 595, }, Object { "x": 1528416000000, - "y": 6160, + "y": 616, }, Object { "x": 1528426800000, - "y": 5855, + "y": 585.5, }, Object { "x": 1528437600000, - "y": 6160, + "y": 616, }, Object { "x": 1528448400000, - "y": 6265, + "y": 626.5, }, Object { "x": 1528459200000, - "y": 6250, + "y": 625, }, Object { "x": 1528470000000, - "y": 5835, + "y": 583.5, }, Object { "x": 1528480800000, - "y": 6290, + "y": 629, }, Object { "x": 1528491600000, - "y": 5740, + "y": 574, }, Object { "x": 1528502400000, - "y": 1420, + "y": 142, }, Object { "x": 1528513200000, - "y": 1200, + "y": 120, }, Object { "x": 1528524000000, - "y": 1365, + "y": 136.5, }, Object { "x": 1528534800000, - "y": 1475, + "y": 147.5, }, Object { "x": 1528545600000, - "y": 1405, + "y": 140.5, }, Object { "x": 1528556400000, - "y": 1500, + "y": 150, }, Object { "x": 1528567200000, - "y": 1320, + "y": 132, }, Object { "x": 1528578000000, - "y": 1300, + "y": 130, }, Object { "x": 1528588800000, - "y": 1395, + "y": 139.5, }, Object { "x": 1528599600000, - "y": 1295, + "y": 129.5, }, Object { "x": 1528610400000, - "y": 1455, + "y": 145.5, }, Object { "x": 1528621200000, - "y": 1240, + "y": 124, }, Object { "x": 1528632000000, - "y": 1555, + "y": 155.5, }, Object { "x": 1528642800000, - "y": 1385, + "y": 138.5, }, Object { "x": 1528653600000, - "y": 1395, + "y": 139.5, }, Object { "x": 1528664400000, - "y": 1375, + "y": 137.5, }, Object { "x": 1528675200000, - "y": 5835, + "y": 583.5, }, Object { "x": 1528686000000, - "y": 6350, + "y": 635, }, Object { "x": 1528696800000, - "y": 5815, + "y": 581.5, }, Object { "x": 1528707600000, - "y": 5775, + "y": 577.5, }, Object { "x": 1528718400000, - "y": 6085, + "y": 608.5, }, Object { "x": 1528729200000, - "y": 6135, + "y": 613.5, }, Object { "x": 1528740000000, - "y": 5970, + "y": 597, }, Object { "x": 1528750800000, - "y": 5765, + "y": 576.5, }, Object { "x": 1528761600000, - "y": 6055, + "y": 605.5, }, Object { "x": 1528772400000, - "y": 6015, + "y": 601.5, }, Object { "x": 1528783200000, - "y": 6345, + "y": 634.5, }, Object { "x": 1528794000000, - "y": 5985, + "y": 598.5, }, Object { "x": 1528804800000, - "y": 5920, + "y": 592, }, Object { "x": 1528815600000, - "y": 5880, + "y": 588, }, Object { "x": 1528826400000, - "y": 5810, + "y": 581, }, Object { "x": 1528837200000, - "y": 6350, + "y": 635, }, Object { "x": 1528848000000, - "y": 6120, + "y": 612, }, Object { "x": 1528858800000, - "y": 6275, + "y": 627.5, }, Object { "x": 1528869600000, - "y": 6035, + "y": 603.5, }, Object { "x": 1528880400000, - "y": 6030, + "y": 603, }, Object { "x": 1528891200000, - "y": 6270, + "y": 627, }, Object { "x": 1528902000000, - "y": 6080, + "y": 608, }, Object { "x": 1528912800000, - "y": 6315, + "y": 631.5, }, Object { "x": 1528923600000, - "y": 6385, + "y": 638.5, }, Object { "x": 1528934400000, - "y": 5915, + "y": 591.5, }, Object { "x": 1528945200000, - "y": 6105, + "y": 610.5, }, Object { "x": 1528956000000, - "y": 5990, + "y": 599, }, Object { "x": 1528966800000, - "y": 6070, + "y": 607, }, Object { "x": 1528977600000, @@ -1972,326 +1975,327 @@ Object { "key": "HTTP 4xx", }, Object { + "avg": 8203.6, "dataPoints": Array [ Object { "x": 1528113600000, - "y": 6045, + "y": 604.5, }, Object { "x": 1528124400000, - "y": 6015, + "y": 601.5, }, Object { "x": 1528135200000, - "y": 5980, + "y": 598, }, Object { "x": 1528146000000, - "y": 6150, + "y": 615, }, Object { "x": 1528156800000, - "y": 6165, + "y": 616.5, }, Object { "x": 1528167600000, - "y": 6360, + "y": 636, }, Object { "x": 1528178400000, - "y": 6090, + "y": 609, }, Object { "x": 1528189200000, - "y": 6085, + "y": 608.5, }, Object { "x": 1528200000000, - "y": 6175, + "y": 617.5, }, Object { "x": 1528210800000, - "y": 6245, + "y": 624.5, }, Object { "x": 1528221600000, - "y": 5790, + "y": 579, }, Object { "x": 1528232400000, - "y": 6075, + "y": 607.5, }, Object { "x": 1528243200000, - "y": 5955, + "y": 595.5, }, Object { "x": 1528254000000, - "y": 6175, + "y": 617.5, }, Object { "x": 1528264800000, - "y": 6060, + "y": 606, }, Object { "x": 1528275600000, - "y": 5900, + "y": 590, }, Object { "x": 1528286400000, - "y": 5455, + "y": 545.5, }, Object { "x": 1528297200000, - "y": 5880, + "y": 588, }, Object { "x": 1528308000000, - "y": 6215, + "y": 621.5, }, Object { "x": 1528318800000, - "y": 6040, + "y": 604, }, Object { "x": 1528329600000, - "y": 6010, + "y": 601, }, Object { "x": 1528340400000, - "y": 6440, + "y": 644, }, Object { "x": 1528351200000, - "y": 6205, + "y": 620.5, }, Object { "x": 1528362000000, - "y": 6075, + "y": 607.5, }, Object { "x": 1528372800000, - "y": 5760, + "y": 576, }, Object { "x": 1528383600000, - "y": 6205, + "y": 620.5, }, Object { "x": 1528394400000, - "y": 5885, + "y": 588.5, }, Object { "x": 1528405200000, - "y": 6215, + "y": 621.5, }, Object { "x": 1528416000000, - "y": 6275, + "y": 627.5, }, Object { "x": 1528426800000, - "y": 5945, + "y": 594.5, }, Object { "x": 1528437600000, - "y": 5915, + "y": 591.5, }, Object { "x": 1528448400000, - "y": 6075, + "y": 607.5, }, Object { "x": 1528459200000, - "y": 6410, + "y": 641, }, Object { "x": 1528470000000, - "y": 5885, + "y": 588.5, }, Object { "x": 1528480800000, - "y": 5995, + "y": 599.5, }, Object { "x": 1528491600000, - "y": 6170, + "y": 617, }, Object { "x": 1528502400000, - "y": 1420, + "y": 142, }, Object { "x": 1528513200000, - "y": 1535, + "y": 153.5, }, Object { "x": 1528524000000, - "y": 1415, + "y": 141.5, }, Object { "x": 1528534800000, - "y": 1515, + "y": 151.5, }, Object { "x": 1528545600000, - "y": 1630, + "y": 163, }, Object { "x": 1528556400000, - "y": 1345, + "y": 134.5, }, Object { "x": 1528567200000, - "y": 1485, + "y": 148.5, }, Object { "x": 1528578000000, - "y": 1390, + "y": 139, }, Object { "x": 1528588800000, - "y": 1445, + "y": 144.5, }, Object { "x": 1528599600000, - "y": 1360, + "y": 136, }, Object { "x": 1528610400000, - "y": 1395, + "y": 139.5, }, Object { "x": 1528621200000, - "y": 1190, + "y": 119, }, Object { "x": 1528632000000, - "y": 1440, + "y": 144, }, Object { "x": 1528642800000, - "y": 1290, + "y": 129, }, Object { "x": 1528653600000, - "y": 1320, + "y": 132, }, Object { "x": 1528664400000, - "y": 1480, + "y": 148, }, Object { "x": 1528675200000, - "y": 6065, + "y": 606.5, }, Object { "x": 1528686000000, - "y": 6270, + "y": 627, }, Object { "x": 1528696800000, - "y": 5675, + "y": 567.5, }, Object { "x": 1528707600000, - "y": 6200, + "y": 620, }, Object { "x": 1528718400000, - "y": 6075, + "y": 607.5, }, Object { "x": 1528729200000, - "y": 6195, + "y": 619.5, }, Object { "x": 1528740000000, - "y": 6045, + "y": 604.5, }, Object { "x": 1528750800000, - "y": 6040, + "y": 604, }, Object { "x": 1528761600000, - "y": 5880, + "y": 588, }, Object { "x": 1528772400000, - "y": 6035, + "y": 603.5, }, Object { "x": 1528783200000, - "y": 5990, + "y": 599, }, Object { "x": 1528794000000, - "y": 5825, + "y": 582.5, }, Object { "x": 1528804800000, - "y": 5940, + "y": 594, }, Object { "x": 1528815600000, - "y": 6225, + "y": 622.5, }, Object { "x": 1528826400000, - "y": 6190, + "y": 619, }, Object { "x": 1528837200000, - "y": 6415, + "y": 641.5, }, Object { "x": 1528848000000, - "y": 5990, + "y": 599, }, Object { "x": 1528858800000, - "y": 5860, + "y": 586, }, Object { "x": 1528869600000, - "y": 6145, + "y": 614.5, }, Object { "x": 1528880400000, - "y": 6195, + "y": 619.5, }, Object { "x": 1528891200000, - "y": 6155, + "y": 615.5, }, Object { "x": 1528902000000, - "y": 6240, + "y": 624, }, Object { "x": 1528912800000, - "y": 6100, + "y": 610, }, Object { "x": 1528923600000, - "y": 6120, + "y": 612, }, Object { "x": 1528934400000, - "y": 5440, + "y": 544, }, Object { "x": 1528945200000, - "y": 6175, + "y": 617.5, }, Object { "x": 1528956000000, - "y": 5805, + "y": 580.5, }, Object { "x": 1528966800000, - "y": 5915, + "y": 591.5, }, Object { "x": 1528977600000, @@ -2301,6 +2305,7 @@ Object { "key": "HTTP 5xx", }, Object { + "avg": 0, "dataPoints": Array [], "key": "A Custom Bucket (that should be last)", }, diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts index 0f1eca5853bb9..8a0fe1a57736f 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts @@ -21,10 +21,12 @@ export async function getApmTimeseriesData(options: { }) { const { start, end } = options.setup; const { bucketSize } = getBucketSize(start, end, 'auto'); + const durationAsMinutes = (end - start) / 1000 / 60; const timeseriesResponse = await timeseriesFetcher(options); return timeseriesTransformer({ timeseriesResponse, bucketSize, + durationAsMinutes, }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts index abce5dbe18059..d0d0875be388f 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts @@ -16,7 +16,8 @@ describe('timeseriesTransformer', () => { beforeEach(async () => { res = await timeseriesTransformer({ timeseriesResponse, - bucketSize: 12, + bucketSize: 120, + durationAsMinutes: 10, }); }); @@ -79,7 +80,7 @@ describe('getTpmBuckets', () => { { key_as_string: '', key: 1, - doc_count: 500, + doc_count: 100, }, { key_as_string: '', @@ -95,23 +96,31 @@ describe('getTpmBuckets', () => { }, }, ]; - const bucketSize = 10; - expect(getTpmBuckets(buckets as any, bucketSize)).toEqual([ + + expect( + getTpmBuckets({ + transactionResultBuckets: buckets, + bucketSize: 120, + durationAsMinutes: 10, + }) + ).toEqual([ { + avg: 90, dataPoints: [ { x: 0, y: 0 }, - { x: 1, y: 1200 }, - { x: 2, y: 1800 }, - { x: 3, y: 2400 }, + { x: 1, y: 100 }, + { x: 2, y: 150 }, + { x: 3, y: 200 }, ], key: 'HTTP 4xx', }, { + avg: 50, dataPoints: [ { x: 0, y: 0 }, - { x: 1, y: 3000 }, - { x: 2, y: 600 }, - { x: 3, y: 1800 }, + { x: 1, y: 50 }, + { x: 2, y: 50 }, + { x: 3, y: 150 }, ], key: 'HTTP 5xx', }, diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index bbdd0bd3e054a..f68c069253b99 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isNumber, round, sortBy } from 'lodash'; +import { isNumber, sortBy } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate } from '../../../../../typings/timeseries'; import { ESResponse } from './fetcher'; @@ -14,16 +14,22 @@ export type ApmTimeSeriesResponse = ReturnType; export function timeseriesTransformer({ timeseriesResponse, bucketSize, + durationAsMinutes, }: { timeseriesResponse: ESResponse; bucketSize: number; + durationAsMinutes: number; }) { const aggs = timeseriesResponse.aggregations; const overallAvgDuration = aggs?.overall_avg_duration.value || null; const responseTimeBuckets = aggs?.response_times.buckets || []; const { avg, p95, p99 } = getResponseTime(responseTimeBuckets); const transactionResultBuckets = aggs?.transaction_results.buckets || []; - const tpmBuckets = getTpmBuckets(transactionResultBuckets, bucketSize); + const tpmBuckets = getTpmBuckets({ + transactionResultBuckets, + bucketSize, + durationAsMinutes, + }); return { responseTimes: { @@ -36,18 +42,28 @@ export function timeseriesTransformer({ }; } -export function getTpmBuckets( - transactionResultBuckets: Required< - ESResponse - >['aggregations']['transaction_results']['buckets'] = [], - bucketSize: number -) { +type TransactionResultBuckets = Required< + ESResponse +>['aggregations']['transaction_results']['buckets']; + +export function getTpmBuckets({ + transactionResultBuckets = [], + bucketSize, + durationAsMinutes, +}: { + transactionResultBuckets: TransactionResultBuckets; + bucketSize: number; + durationAsMinutes: number; +}) { const buckets = transactionResultBuckets.map( ({ key: resultKey, timeseries }) => { const dataPoints = timeseries.buckets.map((bucket) => { + // calculate request/minute. Avoid up-scaling numbers if bucketSize is below 60s (1 minute). + // Eg. 1 request during a 10 second window should be displayed as "1 rpm" instead of "6 rpm". + const tmpValue = bucket.doc_count * (60 / Math.max(60, bucketSize)); return { x: bucket.key, - y: round(bucket.doc_count * (60 / bucketSize), 1), + y: tmpValue, }; }); @@ -55,7 +71,14 @@ export function getTpmBuckets( const key = resultKey === '' ? NOT_AVAILABLE_LABEL : (resultKey as string); - return { key, dataPoints }; + const docCountTotal = timeseries.buckets + .map((bucket) => bucket.doc_count) + .reduce((a, b) => a + b, 0); + + // calculate request/minute + const avg = docCountTotal / durationAsMinutes; + + return { key, dataPoints, avg }; } ); @@ -65,11 +88,11 @@ export function getTpmBuckets( ); } -function getResponseTime( - responseTimeBuckets: Required< - ESResponse - >['aggregations']['response_times']['buckets'] = [] -) { +type ResponseTimeBuckets = Required< + ESResponse +>['aggregations']['response_times']['buckets']; + +function getResponseTime(responseTimeBuckets: ResponseTimeBuckets = []) { return responseTimeBuckets.reduce( (acc, bucket) => { const { '95.0': p95, '99.0': p99 } = bucket.pct.values; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index f0a05dfc0df30..eb781ee078307 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -14,7 +14,7 @@ import { import { Observable, combineLatest } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { ObservabilityPluginSetup } from '../../observability/server'; -import { SecurityPluginSetup } from '../../security/public'; +import { SecurityPluginSetup } from '../../security/server'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { TaskManagerSetupContract } from '../../task_manager/server'; import { AlertingPlugin } from '../../alerts/server'; @@ -28,11 +28,19 @@ import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; import { CloudSetup } from '../../cloud/server'; import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; -import { LicensingPluginSetup } from '../../licensing/public'; +import { + LicensingPluginSetup, + LicensingPluginStart, +} from '../../licensing/server'; import { registerApmAlerts } from './lib/alerts/register_apm_alerts'; import { createApmTelemetry } from './lib/apm_telemetry'; + import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; -import { APM_FEATURE } from './feature'; +import { + APM_FEATURE, + APM_SERVICE_MAPS_FEATURE_NAME, + APM_SERVICE_MAPS_LICENSE_TYPE, +} from './feature'; import { apmIndices, apmTelemetry } from './saved_objects'; import { createElasticCloudInstructions } from './tutorial/elastic_cloud'; import { MlPluginSetup } from '../../ml/server'; @@ -120,16 +128,25 @@ export class APMPlugin implements Plugin { elasticCloud: createElasticCloudInstructions(plugins.cloud), }; }); + plugins.features.registerFeature(APM_FEATURE); + plugins.licensing.featureUsage.register( + APM_SERVICE_MAPS_FEATURE_NAME, + APM_SERVICE_MAPS_LICENSE_TYPE + ); - createApmApi().init(core, { - config$: mergedConfig$, - logger: this.logger!, - plugins: { - observability: plugins.observability, - security: plugins.security, - ml: plugins.ml, - }, + core.getStartServices().then(([_coreStart, pluginsStart]) => { + createApmApi().init(core, { + config$: mergedConfig$, + logger: this.logger!, + plugins: { + licensing: (pluginsStart as { licensing: LicensingPluginStart }) + .licensing, + observability: plugins.observability, + security: plugins.security, + ml: plugins.ml, + }, + }); }); return { diff --git a/x-pack/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/plugins/apm/server/routes/create_api/index.test.ts index 3d3e26f680e0d..f5db936c00d3a 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.test.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.test.ts @@ -9,6 +9,7 @@ import { CoreSetup, Logger } from 'src/core/server'; import { Params } from '../typings'; import { BehaviorSubject } from 'rxjs'; import { APMConfig } from '../..'; +import { LicensingPluginStart } from '../../../../licensing/server'; const getCoreMock = () => { const get = jest.fn(); @@ -40,7 +41,7 @@ const getCoreMock = () => { logger: ({ error: jest.fn(), } as unknown) as Logger, - plugins: {}, + plugins: { licensing: {} as LicensingPluginStart }, }, }; }; diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 774f1f27435a2..bdfb49fa30828 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -13,6 +13,7 @@ import { errorDistributionRoute, errorGroupsRoute, errorsRoute, + errorRateRoute, } from './errors'; import { serviceAgentNameRoute, @@ -81,6 +82,7 @@ const createApmApi = () => { .add(errorDistributionRoute) .add(errorGroupsRoute) .add(errorsRoute) + .add(errorRateRoute) // Services .add(serviceAgentNameRoute) diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index 1615550027d3c..97314a9a61661 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -11,6 +11,7 @@ import { getErrorGroup } from '../lib/errors/get_error_group'; import { getErrorGroups } from '../lib/errors/get_error_groups'; import { setupRequest } from '../lib/helpers/setup_request'; import { uiFiltersRt, rangeRt } from './default_api_types'; +import { getErrorRate } from '../lib/errors/get_error_rate'; export const errorsRoute = createRoute(() => ({ path: '/api/apm/services/{serviceName}/errors', @@ -80,3 +81,26 @@ export const errorDistributionRoute = createRoute(() => ({ return getErrorDistribution({ serviceName, groupId, setup }); }, })); + +export const errorRateRoute = createRoute(() => ({ + path: '/api/apm/services/{serviceName}/errors/rate', + params: { + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + t.partial({ + groupId: t.string, + }), + uiFiltersRt, + rangeRt, + ]), + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { params } = context; + const { serviceName } = params.path; + const { groupId } = params.query; + return getErrorRate({ serviceName, groupId, setup }); + }, +})); diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index df0403be7b975..3937c18b3fe5e 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -15,6 +15,7 @@ import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; import { createRoute } from './create_route'; import { rangeRt } from './default_api_types'; +import { APM_SERVICE_MAPS_FEATURE_NAME } from '../feature'; export const serviceMapRoute = createRoute(() => ({ path: '/api/apm/service-map', @@ -35,6 +36,10 @@ export const serviceMapRoute = createRoute(() => ({ throw Boom.forbidden(invalidLicenseMessage); } + context.plugins.licensing.featureUsage.notifyUsage( + APM_SERVICE_MAPS_FEATURE_NAME + ); + const setup = await setupRequest(context, request); const { query: { serviceName, environment }, diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index bc31cb7a582af..f30a9d18d7aea 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -14,10 +14,11 @@ import { import { PickByValue, Optional } from 'utility-types'; import { Observable } from 'rxjs'; import { Server } from 'hapi'; +import { LicensingPluginStart } from '../../../licensing/server'; import { ObservabilityPluginSetup } from '../../../observability/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FetchOptions } from '../../public/services/rest/callApi'; -import { SecurityPluginSetup } from '../../../security/public'; +import { SecurityPluginSetup } from '../../../security/server'; import { MlPluginSetup } from '../../../ml/server'; import { APMConfig } from '..'; @@ -66,6 +67,7 @@ export type APMRequestHandlerContext< config: APMConfig; logger: Logger; plugins: { + licensing: LicensingPluginStart; observability?: ObservabilityPluginSetup; security?: SecurityPluginSetup; ml?: MlPluginSetup; @@ -114,6 +116,7 @@ export interface ServerAPI { config$: Observable; logger: Logger; plugins: { + licensing: LicensingPluginStart; observability?: ObservabilityPluginSetup; security?: SecurityPluginSetup; ml?: MlPluginSetup; diff --git a/x-pack/plugins/beats_management/server/index.ts b/x-pack/plugins/beats_management/server/index.ts index 607fb0ab2725d..ad19087f5ac9f 100644 --- a/x-pack/plugins/beats_management/server/index.ts +++ b/x-pack/plugins/beats_management/server/index.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializer } from '../../../../src/core/server'; import { beatsManagementConfigSchema } from '../common'; +import { BeatsManagementPlugin } from './plugin'; export const config = { schema: beatsManagementConfigSchema, @@ -16,8 +18,4 @@ export const config = { }, }; -export const plugin = () => ({ - setup() {}, - start() {}, - stop() {}, -}); +export const plugin: PluginInitializer<{}, {}> = (context) => new BeatsManagementPlugin(context); diff --git a/x-pack/plugins/beats_management/server/plugin.ts b/x-pack/plugins/beats_management/server/plugin.ts new file mode 100644 index 0000000000000..a82dbcb4a3a6e --- /dev/null +++ b/x-pack/plugins/beats_management/server/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from '../../../../src/core/server'; +import { SecurityPluginSetup } from '../../security/server'; +import { LicensingPluginStart } from '../../licensing/server'; +import { BeatsManagementConfigType } from '../common'; + +interface SetupDeps { + security?: SecurityPluginSetup; +} + +interface StartDeps { + licensing: LicensingPluginStart; +} + +export class BeatsManagementPlugin implements Plugin<{}, {}, SetupDeps, StartDeps> { + constructor( + private readonly initializerContext: PluginInitializerContext + ) {} + + public async setup(core: CoreSetup, plugins: SetupDeps) { + this.initializerContext.config.create(); + + return {}; + } + + public async start(core: CoreStart, { licensing }: StartDeps) { + return {}; + } +} diff --git a/x-pack/legacy/plugins/beats_management/types/formsy.d.ts b/x-pack/plugins/beats_management/types/formsy.d.ts similarity index 100% rename from x-pack/legacy/plugins/beats_management/types/formsy.d.ts rename to x-pack/plugins/beats_management/types/formsy.d.ts diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.ts b/x-pack/plugins/canvas/common/lib/autocomplete.ts index 0ab549bd14e83..c97879de2137e 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.ts +++ b/x-pack/plugins/canvas/common/lib/autocomplete.ts @@ -14,7 +14,7 @@ import { ExpressionFunction, ExpressionFunctionParameter, getByAlias, -} from '../../../../../src/plugins/expressions'; +} from '../../../../../src/plugins/expressions/common'; const MARKER = 'CANVAS_SUGGESTION_MARKER'; diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts index 81156b98bab83..2da489e643435 100644 --- a/x-pack/plugins/case/server/routes/api/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -222,7 +222,12 @@ describe('Utils', () => { ]; const res = transformCases( - { saved_objects: mockCases, total: mockCases.length, per_page: 10, page: 1 }, + { + saved_objects: mockCases.map((obj) => ({ ...obj, score: 1 })), + total: mockCases.length, + per_page: 10, + page: 1, + }, 2, 2, extraCaseData, @@ -232,7 +237,11 @@ describe('Utils', () => { page: 1, per_page: 10, total: mockCases.length, - cases: flattenCaseSavedObjects(mockCases, extraCaseData, '123'), + cases: flattenCaseSavedObjects( + mockCases.map((obj) => ({ ...obj, score: 1 })), + extraCaseData, + '123' + ), count_open_cases: 2, count_closed_cases: 2, }); @@ -500,7 +509,7 @@ describe('Utils', () => { describe('transformComments', () => { it('transforms correctly', () => { const comments = { - saved_objects: mockCaseComments, + saved_objects: mockCaseComments.map((obj) => ({ ...obj, score: 1 })), total: mockCaseComments.length, per_page: 10, page: 1, diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index b7f3c68d1662f..ec2881807442f 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -101,7 +101,7 @@ export const transformCases = ( }); export const flattenCaseSavedObjects = ( - savedObjects: SavedObjectsFindResponse['saved_objects'], + savedObjects: Array>, totalCommentByCase: TotalCommentByCase[], caseConfigureConnectorId: string = 'none' ): CaseResponse[] => @@ -146,7 +146,7 @@ export const transformComments = ( }); export const flattenCommentSavedObjects = ( - savedObjects: SavedObjectsFindResponse['saved_objects'] + savedObjects: Array> ): CommentResponse[] => savedObjects.reduce((acc: CommentResponse[], savedObject: SavedObject) => { return [...acc, flattenCommentSavedObject(savedObject)]; diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json index 443bb63a27799..1be55d2b7a635 100644 --- a/x-pack/plugins/data_enhanced/kibana.json +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -8,6 +8,7 @@ "requiredPlugins": [ "data" ], + "optionalPlugins": ["kibanaReact", "kibanaUtils"], "server": true, "ui": true } diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index a27a73431574b..4f6756231912c 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -22,10 +22,9 @@ export class EnhancedDataServerPlugin implements Plugin { - const mockCoreSetup = coreMock.createSetup(); const mockApiCaller = jest.fn(); - const mockSearch = jest.fn(); + const mockContext = { + core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } }, + }; const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; beforeEach(() => { mockApiCaller.mockClear(); - mockSearch.mockClear(); }); - it('returns a strategy with `search`', () => { - const esSearch = enhancedEsSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + it('returns a strategy with `search`', async () => { + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); expect(typeof esSearch.search).toBe('function'); }); @@ -56,16 +50,9 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = enhancedEsSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); - await esSearch.search({ params }); + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); @@ -79,16 +66,9 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = enhancedEsSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); - await esSearch.search({ id: 'foo', params }); + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); @@ -102,16 +82,9 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = enhancedEsSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); - await esSearch.search({ params }); + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); @@ -124,16 +97,12 @@ describe('ES search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockRollupResponse); const params = { index: 'foo-程', body: {} }; - const esSearch = enhancedEsSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); - - await esSearch.search({ indexType: 'rollup', params }); + const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$); + + await esSearch.search((mockContext as unknown) as RequestHandlerContext, { + indexType: 'rollup', + params, + }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 15f2ca10af7f7..9083ab24a4521 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -7,16 +7,16 @@ import { first } from 'rxjs/operators'; import { mapKeys, snakeCase } from 'lodash'; import { SearchResponse } from 'elasticsearch'; -import { APICaller } from '../../../../../src/core/server'; +import { Observable } from 'rxjs'; +import { APICaller, SharedGlobalConfig } from '../../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../../src/plugins/data/common'; import { - ISearchContext, - TSearchStrategyProvider, ISearch, ISearchOptions, ISearchCancel, getDefaultSearchParams, getTotalLoaded, + ISearchStrategy, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; import { shimHitsTotal } from './shim_hits_total'; @@ -28,15 +28,16 @@ export interface AsyncSearchResponse { response: SearchResponse; } -export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( - context: ISearchContext, - caller: APICaller -) => { +export const enhancedEsSearchStrategyProvider = ( + config$: Observable +): ISearchStrategy => { const search: ISearch = async ( + context, request: IEnhancedEsSearchRequest, options ) => { - const config = await context.config$.pipe(first()).toPromise(); + const config = await config$.pipe(first()).toPromise(); + const caller = context.core.elasticsearch.legacy.client.callAsCurrentUser; const defaultParams = getDefaultSearchParams(config); const params = { ...defaultParams, ...request.params }; @@ -45,10 +46,13 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = async (id) => { + const cancel: ISearchCancel = async (context, id) => { const method = 'DELETE'; const path = encodeURI(`/_async_search/${id}`); - await caller('transport.request', { method, path }); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { + method, + path, + }); }; return { search, cancel }; diff --git a/x-pack/plugins/discover_enhanced/kibana.json b/x-pack/plugins/discover_enhanced/kibana.json new file mode 100644 index 0000000000000..25b2a83baf98f --- /dev/null +++ b/x-pack/plugins/discover_enhanced/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "discoverEnhanced", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["uiActions", "embeddable", "discover"], + "optionalPlugins": ["share"] +} diff --git a/x-pack/plugins/discover_enhanced/public/actions/index.ts b/x-pack/plugins/discover_enhanced/public/actions/index.ts new file mode 100644 index 0000000000000..cbb955fa46340 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/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 './view_in_discover'; diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.test.ts new file mode 100644 index 0000000000000..a7167d2e2e691 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.test.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ExploreDataContextMenuAction, + ACTION_EXPLORE_DATA, + Params, + PluginDeps, +} from './explore_data_context_menu_action'; +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public'; +import { i18n } from '@kbn/i18n'; +import { + VisualizeEmbeddableContract, + VISUALIZE_EMBEDDABLE_TYPE, +} from '../../../../../../src/plugins/visualizations/public'; +import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; + +const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance; + +jest.mock('@kbn/i18n', () => ({ + i18n: { + translate: jest.fn((key, options) => options.defaultMessage), + }, +})); + +afterEach(() => { + i18nTranslateSpy.mockClear(); +}); + +const setup = () => { + type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>; + + const core = coreMock.createStart(); + + const urlGenerator: UrlGenerator = ({ + id: ACTION_EXPLORE_DATA, + createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')), + } as unknown) as UrlGenerator; + + const plugins: PluginDeps = { + discover: { + urlGenerator, + }, + }; + + const params: Params = { + start: () => ({ + plugins, + self: {}, + core, + }), + }; + const action = new ExploreDataContextMenuAction(params); + + const input = { + viewMode: ViewMode.VIEW, + }; + + const output = { + indexPatterns: [ + { + id: 'index-ptr-foo', + }, + ], + }; + + const embeddable: VisualizeEmbeddableContract = ({ + type: VISUALIZE_EMBEDDABLE_TYPE, + getInput: () => input, + getOutput: () => output, + } as unknown) as VisualizeEmbeddableContract; + + const context = { + embeddable, + }; + + return { core, plugins, urlGenerator, params, action, input, output, embeddable, context }; +}; + +describe('"Explore underlying data" panel action', () => { + test('action has Discover icon', () => { + const { action } = setup(); + expect(action.getIconType()).toBe('discoverApp'); + }); + + test('title is "Explore underlying data"', () => { + const { action } = setup(); + expect(action.getDisplayName()).toBe('Explore underlying data'); + }); + + test('translates title', () => { + expect(i18nTranslateSpy).toHaveBeenCalledTimes(0); + + setup().action.getDisplayName(); + + expect(i18nTranslateSpy).toHaveBeenCalledTimes(1); + expect(i18nTranslateSpy.mock.calls[0][0]).toBe( + 'xpack.discover.FlyoutCreateDrilldownAction.displayName' + ); + }); + + describe('isCompatible()', () => { + test('returns true when all conditions are met', async () => { + const { action, context } = setup(); + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(true); + }); + + test('returns false when URL generator is not present', async () => { + const { action, plugins, context } = setup(); + (plugins.discover as any).urlGenerator = undefined; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable is not Visualize embeddable', async () => { + const { action, embeddable, context } = setup(); + (embeddable as any).type = 'NOT_VISUALIZE_EMBEDDABLE'; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable does not have index patterns', async () => { + const { action, output, context } = setup(); + delete output.indexPatterns; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if embeddable index patterns are empty', async () => { + const { action, output, context } = setup(); + output.indexPatterns = []; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + + test('returns false if dashboard is in edit mode', async () => { + const { action, input, context } = setup(); + input.viewMode = ViewMode.EDIT; + + const isCompatible = await action.isCompatible(context); + + expect(isCompatible).toBe(false); + }); + }); + + describe('getHref()', () => { + test('returns URL path generated by URL generator', async () => { + const { action, context } = setup(); + + const href = await action.getHref(context); + + expect(href).toBe('/xyz/app/discover/foo#bar'); + }); + + test('calls URL generator with right arguments', async () => { + const { action, urlGenerator, context } = setup(); + + expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0); + + await action.getHref(context); + + expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1); + expect(urlGenerator.createUrl).toHaveBeenCalledWith({ + indexPatternId: 'index-ptr-foo', + }); + }); + }); + + describe('execute()', () => { + test('calls platform SPA navigation method', async () => { + const { action, context, core } = setup(); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(0); + + await action.execute(context); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(1); + }); + + test('calls platform SPA navigation method with right arguments', async () => { + const { action, context, core } = setup(); + + await action.execute(context); + + expect(core.application.navigateToApp).toHaveBeenCalledTimes(1); + expect(core.application.navigateToApp.mock.calls[0]).toEqual([ + 'discover', + { + path: '/foo#bar', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts new file mode 100644 index 0000000000000..d66ca129934a8 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/explore_data_context_menu_action.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable max-classes-per-file */ + +import { i18n } from '@kbn/i18n'; +import { Action } from '../../../../../../src/plugins/ui_actions/public'; +import { DiscoverStart } from '../../../../../../src/plugins/discover/public'; +import { + EmbeddableContext, + IEmbeddable, + ViewMode, +} from '../../../../../../src/plugins/embeddable/public'; +import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public'; +import { CoreStart } from '../../../../../../src/core/public'; +import { + VisualizeEmbeddableContract, + VISUALIZE_EMBEDDABLE_TYPE, +} from '../../../../../../src/plugins/visualizations/public'; + +// TODO: Replace this logic with KibanaURL once it is available. +// https://github.com/elastic/kibana/issues/64497 +class KibanaURL { + public readonly path: string; + public readonly appName: string; + public readonly appPath: string; + + constructor(path: string) { + const match = path.match(/^.*\/app\/([^\/#]+)(.+)$/); + + if (!match) { + throw new Error('Unexpected Discover URL path.'); + } + + const [, appName, appPath] = match; + + if (!appName || !appPath) { + throw new Error('Could not parse Discover URL path.'); + } + + this.path = path; + this.appName = appName; + this.appPath = appPath; + } +} + +export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; + +const isOutputWithIndexPatterns = ( + output: unknown +): output is { indexPatterns: Array<{ id: string }> } => { + if (!output || typeof output !== 'object') return false; + return Array.isArray((output as any).indexPatterns); +}; + +const isVisualizeEmbeddable = ( + embeddable: IEmbeddable +): embeddable is VisualizeEmbeddableContract => embeddable?.type === VISUALIZE_EMBEDDABLE_TYPE; + +export interface PluginDeps { + discover: Pick; +} + +export interface CoreDeps { + application: Pick; +} + +export interface Params { + start: StartServicesGetter; +} + +export class ExploreDataContextMenuAction implements Action { + public readonly id = ACTION_EXPLORE_DATA; + + public readonly type = ACTION_EXPLORE_DATA; + + public readonly order = 200; + + constructor(private readonly params: Params) {} + + public getDisplayName() { + return i18n.translate('xpack.discover.FlyoutCreateDrilldownAction.displayName', { + defaultMessage: 'Explore underlying data', + }); + } + + public getIconType() { + return 'discoverApp'; + } + + public async isCompatible({ embeddable }: EmbeddableContext) { + if (!this.params.start().plugins.discover.urlGenerator) return false; + if (!isVisualizeEmbeddable(embeddable)) return false; + if (!this.getIndexPattern(embeddable)) return false; + if (embeddable.getInput().viewMode !== ViewMode.VIEW) return false; + return true; + } + + public async execute({ embeddable }: EmbeddableContext) { + if (!isVisualizeEmbeddable(embeddable)) return; + + const { core } = this.params.start(); + const { appName, appPath } = await this.getUrl(embeddable); + + await core.application.navigateToApp(appName, { + path: appPath, + }); + } + + public async getHref({ embeddable }: EmbeddableContext): Promise { + if (!isVisualizeEmbeddable(embeddable)) { + throw new Error(`Embeddable not supported for "${this.getDisplayName()}" action.`); + } + + const { path } = await this.getUrl(embeddable); + + return path; + } + + private async getUrl(embeddable: VisualizeEmbeddableContract): Promise { + const { plugins } = this.params.start(); + const { urlGenerator } = plugins.discover; + + if (!urlGenerator) { + throw new Error('Discover URL generator not available.'); + } + + const { timeRange, query, filters } = embeddable.getInput(); + const indexPatternId = this.getIndexPattern(embeddable); + + const path = await urlGenerator.createUrl({ + indexPatternId, + filters, + query, + timeRange, + }); + + return new KibanaURL(path); + } + + /** + * @returns Returns empty string if no index pattern ID found. + */ + private getIndexPattern(embeddable: VisualizeEmbeddableContract): string { + const output = embeddable!.getOutput(); + + if (isOutputWithIndexPatterns(output) && output.indexPatterns.length > 0) { + return output.indexPatterns[0].id; + } + + return ''; + } +} diff --git a/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/index.ts b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/index.ts new file mode 100644 index 0000000000000..8788621365385 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/actions/view_in_discover/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 './explore_data_context_menu_action'; diff --git a/x-pack/plugins/discover_enhanced/public/index.ts b/x-pack/plugins/discover_enhanced/public/index.ts new file mode 100644 index 0000000000000..943a212dd7c4e --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { DiscoverEnhancedPlugin } from './plugin'; + +export { + DiscoverEnhancedPlugin, + DiscoverEnhancedSetupDependencies, + DiscoverEnhancedStartDependencies, +} from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new DiscoverEnhancedPlugin(initializerContext); diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts new file mode 100644 index 0000000000000..f55c5dab3449b --- /dev/null +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { PluginInitializerContext } from 'kibana/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; +import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; +import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; +import { + EmbeddableSetup, + EmbeddableStart, + EmbeddableContext, + CONTEXT_MENU_TRIGGER, +} from '../../../../src/plugins/embeddable/public'; +import { ExploreDataContextMenuAction, ACTION_EXPLORE_DATA } from './actions'; + +declare module '../../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [ACTION_EXPLORE_DATA]: EmbeddableContext; + } +} + +export interface DiscoverEnhancedSetupDependencies { + discover: DiscoverSetup; + embeddable: EmbeddableSetup; + share?: SharePluginSetup; + uiActions: UiActionsSetup; +} + +export interface DiscoverEnhancedStartDependencies { + discover: DiscoverStart; + embeddable: EmbeddableStart; + share?: SharePluginStart; + uiActions: UiActionsStart; +} + +export class DiscoverEnhancedPlugin + implements + Plugin { + constructor(public readonly initializerContext: PluginInitializerContext) {} + + setup( + core: CoreSetup, + { uiActions, share }: DiscoverEnhancedSetupDependencies + ) { + const start = createStartServicesGetter(core.getStartServices); + + if (!!share) { + const exploreDataAction = new ExploreDataContextMenuAction({ start }); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, exploreDataAction); + } + } + + start(core: CoreStart, plugins: DiscoverEnhancedStartDependencies) {} + + stop() {} +} diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index 45cf7365ebd91..e05547741871e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -51,10 +51,12 @@ export function createFlyoutManageDrilldowns({ uiActionsEnhanced, storage, notifications, + docsLink, }: { uiActionsEnhanced: AdvancedUiActionsStart; storage: IStorageWrapper; notifications: NotificationsStart; + docsLink?: string; }) { // fine to assume this is static, // because all action factories should be registered in setup phase @@ -145,6 +147,7 @@ export function createFlyoutManageDrilldowns({ case Routes.Edit: return ( = ({ {docsLink && ( <> - {txtViewDocsLinkLabel} + + {txtViewDocsLinkLabel} + )} diff --git a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx index 87f886817517f..8994aac4123e1 100644 --- a/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx +++ b/x-pack/plugins/drilldowns/public/components/flyout_drilldown_wizard/flyout_drilldown_wizard.tsx @@ -39,6 +39,8 @@ export interface FlyoutDrilldownWizardProps void; actionFactoryContext?: object; + + docsLink?: string; } function useWizardConfigState( @@ -118,6 +120,7 @@ export function FlyoutDrilldownWizard) { const [wizardConfig, { setActionFactory, setActionConfig, setName }] = useWizardConfigState( initialDrilldownWizardConfig @@ -154,7 +157,11 @@ export function FlyoutDrilldownWizard} + banner={ + showWelcomeMessage && ( + + ) + } > void; onCreate?: () => void; @@ -21,6 +22,7 @@ export interface FlyoutListManageDrilldownsProps { } export function FlyoutListManageDrilldowns({ + docsLink, drilldowns, onClose = () => {}, onCreate, @@ -33,7 +35,11 @@ export function FlyoutListManageDrilldowns({ } + banner={ + showWelcomeMessage && ( + + ) + } > { + if (!(context as EmbeddableContext)?.embeddable) { + // eslint-disable-next-line no-console + console.warn('For drilldowns to work action context should contain .embeddable field.'); + return false; + } + return true; + }; + private enhanceEmbeddableWithDynamicActions( embeddable: E ): EnhancedEmbeddable { @@ -114,13 +124,9 @@ export class EmbeddableEnhancedPlugin const storage = new EmbeddableActionStorage(embeddable as EmbeddableWithDynamicActions); const dynamicActions = new DynamicActionManager({ isCompatible: async (context: unknown) => { - if (!(context as EmbeddableContext)?.embeddable) { - // eslint-disable-next-line no-console - console.warn('For drilldowns to work action context should contain .embeddable field.'); - return false; - } - - return (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId; + if (!this.isEmbeddableContext(context)) return false; + if (context.embeddable.getInput().viewMode !== ViewMode.VIEW) return false; + return context.embeddable.runtimeId === embeddable.runtimeId; }, storage, uiActions: this.uiActions!, diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts index 7098f611defa0..ec5d81532e238 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts @@ -676,12 +676,14 @@ describe('#find', () => { id: 'some-id', type: 'unknown-type', attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, + score: 1, references: [], }, { id: 'some-id-2', type: 'unknown-type', attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, + score: 1, references: [], }, ], @@ -722,6 +724,7 @@ describe('#find', () => { attrNotSoSecret: 'not-so-secret', attrThree: 'three', }, + score: 1, references: [], }, { @@ -733,6 +736,7 @@ describe('#find', () => { attrNotSoSecret: '*not-so-secret*', attrThree: 'three', }, + score: 1, references: [], }, ], @@ -793,6 +797,7 @@ describe('#find', () => { attrNotSoSecret: 'not-so-secret', attrThree: 'three', }, + score: 1, references: [], }, { @@ -804,6 +809,7 @@ describe('#find', () => { attrNotSoSecret: '*not-so-secret*', attrThree: 'three', }, + score: 1, references: [], }, ], diff --git a/x-pack/plugins/features/kibana.json b/x-pack/plugins/features/kibana.json index 1cab1821b1bf5..92fdd08e93478 100644 --- a/x-pack/plugins/features/kibana.json +++ b/x-pack/plugins/features/kibana.json @@ -6,5 +6,6 @@ "optionalPlugins": ["visTypeTimelion"], "configPath": ["xpack", "features"], "server": true, - "ui": true + "ui": true, + "extraPublicDirs": ["common"] } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js index 28bc8671f29e2..cd690c768a326 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js @@ -188,7 +188,7 @@ export const MinAgeInput = (props) => { errors={errors} helpText={ { + const I18nContext = ({ children }: any) => children; + return { I18nContext }; +}); + +const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = { + name: 'comp-1', + template: { + mappings: { properties: { ip_address: { type: 'ip' } } }, + aliases: { mydata: {} }, + settings: { number_of_shards: 1 }, + }, + version: 1, + _meta: { description: 'component template test' }, + _kbnMeta: { usedBy: ['template_1'] }, +}; + +const COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS: ComponentTemplateDeserialized = { + name: 'comp-base', + template: {}, + _kbnMeta: { usedBy: [] }, +}; + +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: ComponentTemplateDetailsTestBed; + + afterAll(() => { + server.restore(); + }); + + describe('With component template details', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadComponentTemplateResponse(COMPONENT_TEMPLATE); + + await act(async () => { + testBed = setup({ + componentTemplateName: COMPONENT_TEMPLATE.name, + onClose: () => {}, + }); + }); + + testBed.component.update(); + }); + + test('renders the details flyout', () => { + const { exists, find, actions, component } = testBed; + + // Verify flyout exists with correct title + expect(exists('componentTemplateDetails')).toBe(true); + expect(find('componentTemplateDetails.title').text()).toBe(COMPONENT_TEMPLATE.name); + + // Verify footer does not display since "actions" prop was not provided + expect(exists('componentTemplateDetails.footer')).toBe(false); + + // Verify tabs exist + expect(exists('settingsTab')).toBe(true); + expect(exists('mappingsTab')).toBe(true); + expect(exists('aliasesTab')).toBe(true); + // Summary tab should be active by default + expect(find('summaryTab').props()['aria-selected']).toBe(true); + + // [Summary tab] Verify description list items + expect(exists('summaryTabContent.usedByTitle')).toBe(true); + expect(exists('summaryTabContent.versionTitle')).toBe(true); + expect(exists('summaryTabContent.metaTitle')).toBe(true); + + // [Settings tab] Navigate to tab and verify content + act(() => { + actions.clickSettingsTab(); + }); + + component.update(); + + expect(exists('settingsTabContent')).toBe(true); + + // [Mappings tab] Navigate to tab and verify content + act(() => { + actions.clickMappingsTab(); + }); + + component.update(); + expect(exists('mappingsTabContent')).toBe(true); + + // [Aliases tab] Navigate to tab and verify content + act(() => { + actions.clickAliasesTab(); + }); + + component.update(); + expect(exists('aliasesTabContent')).toBe(true); + }); + }); + + describe('With only required component template fields', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadComponentTemplateResponse( + COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS + ); + + await act(async () => { + testBed = setup({ + componentTemplateName: COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS.name, + onClose: () => {}, + }); + }); + + testBed.component.update(); + }); + + test('renders the details flyout', () => { + const { exists, actions, component } = testBed; + + // [Summary tab] Verify optional description list items do not display + expect(exists('summaryTabContent.usedByTitle')).toBe(false); + expect(exists('summaryTabContent.versionTitle')).toBe(false); + expect(exists('summaryTabContent.metaTitle')).toBe(false); + // Verify callout renders indicating the component template is not in use + expect(exists('notInUseCallout')).toBe(true); + + // [Settings tab] Navigate to tab and verify info callout + act(() => { + actions.clickSettingsTab(); + }); + + component.update(); + + expect(exists('noSettingsCallout')).toBe(true); + + // [Mappings tab] Navigate to tab and verify info callout + act(() => { + actions.clickMappingsTab(); + }); + + component.update(); + expect(exists('noMappingsCallout')).toBe(true); + + // [Aliases tab] Navigate to tab and verify info callout + act(() => { + actions.clickAliasesTab(); + }); + + component.update(); + expect(exists('noAliasesCallout')).toBe(true); + }); + }); + + describe('With actions', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadComponentTemplateResponse(COMPONENT_TEMPLATE); + + await act(async () => { + testBed = setup({ + componentTemplateName: COMPONENT_TEMPLATE.name, + onClose: () => {}, + actions: [ + { + name: 'Test', + icon: 'info', + closePopoverOnClick: true, + handleActionClick: () => {}, + }, + ], + }); + }); + + testBed.component.update(); + }); + + test('should render a footer with context menu', () => { + const { exists, actions, component, find } = testBed; + + // Verify footer exists + expect(exists('componentTemplateDetails.footer')).toBe(true); + expect(exists('manageComponentTemplateButton')).toBe(true); + + // Click manage button and verify actions + act(() => { + actions.clickManageButton(); + }); + + component.update(); + + expect(exists('manageComponentTemplateContextMenu')).toBe(true); + expect(find('manageComponentTemplateContextMenu.action').length).toEqual(1); + }); + }); + + describe('Error handling', () => { + const error = { + status: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadComponentTemplateResponse(undefined, { body: error }); + + await act(async () => { + testBed = setup({ + componentTemplateName: COMPONENT_TEMPLATE.name, + onClose: () => {}, + }); + }); + + testBed.component.update(); + }); + + test('should render an error message if error fetching pipelines', async () => { + const { exists, find } = testBed; + + expect(exists('sectionError')).toBe(true); + expect(find('sectionError').text()).toContain('Error loading component template'); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index 830cc0ee6a980..86eb88017b77f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -6,10 +6,11 @@ import { act } from 'react-dom/test-utils'; +import { ComponentTemplateListItem } from '../../shared_imports'; + import { setupEnvironment, pageHelpers } from './helpers'; import { ComponentTemplateListTestBed } from './helpers/component_template_list.helpers'; -import { API_BASE_PATH } from '../../../../../../common/constants'; -import { ComponentTemplateListItem } from '../../types'; +import { API_BASE_PATH } from './helpers/constants'; const { setup } = pageHelpers.componentTemplateList; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts new file mode 100644 index 0000000000000..25c2d654fd900 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { registerTestBed, TestBed } from '../../../../../../../../../test_utils'; +import { WithAppDependencies } from './setup_environment'; +import { ComponentTemplateDetailsFlyout } from '../../../component_template_details'; + +export type ComponentTemplateDetailsTestBed = TestBed & { + actions: ReturnType; +}; + +const createActions = (testBed: TestBed) => { + const { find } = testBed; + + /** + * User Actions + */ + const clickSettingsTab = () => { + find('settingsTab').simulate('click'); + }; + + const clickMappingsTab = () => { + find('mappingsTab').simulate('click'); + }; + + const clickAliasesTab = () => { + find('aliasesTab').simulate('click'); + }; + + const clickManageButton = () => { + find('manageComponentTemplateButton').simulate('click'); + }; + + return { + clickSettingsTab, + clickAliasesTab, + clickMappingsTab, + clickManageButton, + }; +}; + +export const setup = (props: any): ComponentTemplateDetailsTestBed => { + const setupTestBed = registerTestBed( + WithAppDependencies(ComponentTemplateDetailsFlyout), + { + memoryRouter: { + wrapComponent: false, + }, + defaultProps: props, + } + ); + + const testBed = setupTestBed() as ComponentTemplateDetailsTestBed; + + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +export type ComponentTemplateDetailsTestSubjects = + | 'componentTemplateDetails' + | 'componentTemplateDetails.title' + | 'componentTemplateDetails.footer' + | 'summaryTab' + | 'mappingsTab' + | 'settingsTab' + | 'aliasesTab' + | 'sectionError' + | 'summaryTabContent' + | 'summaryTabContent.usedByTitle' + | 'summaryTabContent.versionTitle' + | 'summaryTabContent.metaTitle' + | 'notInUseCallout' + | 'aliasesTabContent' + | 'noAliasesCallout' + | 'mappingsTabContent' + | 'noMappingsCallout' + | 'settingsTabContent' + | 'noSettingsCallout' + | 'manageComponentTemplateButton' + | 'manageComponentTemplateContextMenu' + | 'manageComponentTemplateContextMenu.action'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/constants.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/constants.ts new file mode 100644 index 0000000000000..00b07fadd0c08 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/constants.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const API_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts index 8473041ee0af3..b7b674292dd98 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts @@ -5,11 +5,15 @@ */ import sinon, { SinonFakeServer } from 'sinon'; -import { API_BASE_PATH } from '../../../../../../../common'; +import { ComponentTemplateListItem, ComponentTemplateDeserialized } from '../../../shared_imports'; +import { API_BASE_PATH } from './constants'; // Register helpers to mock HTTP Requests const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { - const setLoadComponentTemplatesResponse = (response?: any[], error?: any) => { + const setLoadComponentTemplatesResponse = ( + response?: ComponentTemplateListItem[], + error?: any + ) => { const status = error ? error.status || 400 : 200; const body = error ? error.body : response; @@ -20,6 +24,20 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setLoadComponentTemplateResponse = ( + response?: ComponentTemplateDeserialized, + error?: any + ) => { + const status = error ? error.status || 400 : 200; + const body = error ? error.body : response; + + server.respondWith('GET', `${API_BASE_PATH}/component_templates/:name`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + const setDeleteComponentTemplateResponse = (response?: object) => { server.respondWith('DELETE', `${API_BASE_PATH}/component_templates/:name`, [ 200, @@ -31,6 +49,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { return { setLoadComponentTemplatesResponse, setDeleteComponentTemplateResponse, + setLoadComponentTemplateResponse, }; }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts index c1d75b3c2dd9b..93eb65aac0761 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts @@ -5,6 +5,7 @@ */ import { setup as componentTemplatesListSetup } from './component_template_list.helpers'; +import { setup as componentTemplateDetailsSetup } from './component_template_details.helpers'; export { nextTick, getRandomString, findTestSubject } from '../../../../../../../../../test_utils'; @@ -12,4 +13,5 @@ export { setupEnvironment } from './setup_environment'; export const pageHelpers = { componentTemplateList: { setup: componentTemplatesListSetup }, + componentTemplateDetails: { setup: componentTemplateDetailsSetup }, }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx index c0aeb70166b5b..a2194bbfa0186 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx @@ -9,21 +9,21 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { HttpSetup } from 'kibana/public'; -import { BASE_PATH, API_BASE_PATH } from '../../../../../../../common/constants'; import { notificationServiceMock, docLinksServiceMock, } from '../../../../../../../../../../src/core/public/mocks'; -import { init as initHttpRequests } from './http_requests'; import { ComponentTemplatesProvider } from '../../../component_templates_context'; +import { init as initHttpRequests } from './http_requests'; +import { API_BASE_PATH } from './constants'; + const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); const appDependencies = { httpClient: (mockHttpClient as unknown) as HttpSetup, apiBasePath: API_BASE_PATH, - appBasePath: BASE_PATH, trackMetric: () => {}, docLinks: docLinksServiceMock.createStartContract(), toasts: notificationServiceMock.createSetupContract().toasts, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx new file mode 100644 index 0000000000000..a8007c6363584 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiSpacer, + EuiCallOut, +} from '@elastic/eui'; + +import { SectionLoading, TabSettings, TabAliases, TabMappings } from '../shared_imports'; +import { useComponentTemplatesContext } from '../component_templates_context'; +import { TabSummary } from './tab_summary'; +import { ComponentTemplateTabs, TabType } from './tabs'; +import { ManageButton, ManageAction } from './manage_button'; + +interface Props { + componentTemplateName: string; + onClose: () => void; + showFooter?: boolean; + actions?: ManageAction[]; +} + +export const ComponentTemplateDetailsFlyout: React.FunctionComponent = ({ + componentTemplateName, + onClose, + actions, +}) => { + const { api } = useComponentTemplatesContext(); + + const { data: componentTemplateDetails, isLoading, error } = api.useLoadComponentTemplate( + componentTemplateName + ); + + const [activeTab, setActiveTab] = useState('summary'); + + let content: React.ReactNode | undefined; + + if (isLoading) { + content = ( + + + + ); + } else if (error) { + content = ( + + } + color="danger" + iconType="alert" + data-test-subj="sectionError" + > +

{error.message}

+
+ ); + } else if (componentTemplateDetails) { + const { + template: { settings, mappings, aliases }, + } = componentTemplateDetails; + + const tabToComponentMap: Record = { + summary: , + settings: , + mappings: , + aliases: , + }; + + const tabContent = tabToComponentMap[activeTab]; + + content = ( + <> + + + + + {tabContent} + + ); + } + + return ( + + + +

+ {componentTemplateName} +

+
+
+ + {content} + + {actions && ( + + + {/* "Close" link */} + + + + + + + {/* "Manage" context menu */} + {componentTemplateDetails && ( + + + + )} + + + )} +
+ ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/index.ts new file mode 100644 index 0000000000000..11aac200a2f14 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/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 { ComponentTemplateDetailsFlyout } from './component_template_details'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx new file mode 100644 index 0000000000000..c3a4f9b4dce35 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPopover, + EuiButton, + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, +} from '@elastic/eui'; +import { ComponentTemplateDeserialized } from '../shared_imports'; + +export interface ManageAction { + name: string; + icon: string; + handleActionClick: () => void; + getIsDisabled?: (data: ComponentTemplateDeserialized) => boolean; + closePopoverOnClick?: boolean; +} + +interface Props { + actions: ManageAction[]; + componentTemplateDetails: ComponentTemplateDeserialized; +} + +export const ManageButton: React.FunctionComponent = ({ + actions, + componentTemplateDetails, +}) => { + const [isPopoverOpen, setIsPopOverOpen] = useState(false); + + const items: EuiContextMenuPanelItemDescriptor[] = actions.map( + ({ name, icon, getIsDisabled, closePopoverOnClick, handleActionClick }) => { + const isDisabled = getIsDisabled ? getIsDisabled(componentTemplateDetails) : false; + + return { + name, + icon, + disabled: isDisabled, + toolTipContent: isDisabled ? ( + + ) : null, + onClick: () => { + handleActionClick(); + + if (closePopoverOnClick) { + setIsPopOverOpen(false); + } + }, + 'data-test-subj': 'action', + }; + } + ); + + return ( + setIsPopOverOpen((prevBoolean) => !prevBoolean)} + > + + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopOverOpen(false)} + panelPaddingSize="none" + withTitle + anchorPosition="rightUp" + repositionOnScroll + > + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx new file mode 100644 index 0000000000000..401186f6c962e --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiCodeBlock, + EuiTitle, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; + +import { ComponentTemplateDeserialized } from '../shared_imports'; + +interface Props { + componentTemplateDetails: ComponentTemplateDeserialized; +} + +export const TabSummary: React.FunctionComponent = ({ componentTemplateDetails }) => { + const { version, _meta, _kbnMeta } = componentTemplateDetails; + + const { usedBy } = _kbnMeta; + const templateIsInUse = usedBy.length > 0; + + return ( + <> + {/* Callout when component template is not in use */} + {!templateIsInUse && ( + <> + + } + iconType="pin" + data-test-subj="notInUseCallout" + size="s" + /> + + + )} + + {/* Summary description list */} + + {/* Used by templates */} + {templateIsInUse && ( + <> + + + + +
    + {usedBy.map((templateName: string) => ( +
  • + + {templateName} + +
  • + ))} +
+
+ + )} + + {/* Version (optional) */} + {version && ( + <> + + + + {version} + + )} + + {/* Metadata (optional) */} + {_meta && ( + <> + + + + + {JSON.stringify(_meta, null, 2)} + + + )} +
+ + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tabs.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tabs.tsx new file mode 100644 index 0000000000000..89370e3f6f1b8 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tabs.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiTab, EuiTabs } from '@elastic/eui'; + +export type TabType = 'summary' | 'mappings' | 'aliases' | 'settings'; + +interface Props { + setActiveTab: (id: TabType) => void; + activeTab: TabType; +} + +interface Tab { + id: TabType; + name: string; +} + +const TABS: Tab[] = [ + { + id: 'summary', + name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.summaryTabTitle', { + defaultMessage: 'Summary', + }), + }, + { + id: 'settings', + name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.settingsTabTitle', { + defaultMessage: 'Settings', + }), + }, + { + id: 'mappings', + name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.mappingsTabTitle', { + defaultMessage: 'Mappings', + }), + }, + { + id: 'aliases', + name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.aliasesTabTitle', { + defaultMessage: 'Aliases', + }), + }, +]; + +export const ComponentTemplateTabs: React.FunctionComponent = ({ + setActiveTab, + activeTab, +}) => { + return ( + + {TABS.map((tab) => ( + { + setActiveTab(tab.id); + }} + isSelected={tab.id === activeTab} + key={tab.id} + data-test-subj={`${tab.id}Tab`} + > + {tab.name} + + ))} + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index 41fa608ef538b..05a5ed462d8f7 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -5,24 +5,39 @@ */ import React, { useState, useEffect } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ScopedHistory } from 'kibana/public'; -import { SectionLoading } from '../shared_imports'; -import { useComponentTemplatesContext } from '../component_templates_context'; +import { SectionLoading, ComponentTemplateDeserialized } from '../shared_imports'; import { UIM_COMPONENT_TEMPLATE_LIST_LOAD } from '../constants'; - +import { useComponentTemplatesContext } from '../component_templates_context'; +import { ComponentTemplateDetailsFlyout } from '../component_template_details'; import { EmptyPrompt } from './empty_prompt'; import { ComponentTable } from './table'; import { LoadError } from './error'; import { ComponentTemplatesDeleteModal } from './delete_modal'; -export const ComponentTemplateList: React.FunctionComponent = () => { +interface Props { + componentTemplateName?: string; + history: RouteComponentProps['history']; +} + +export const ComponentTemplateList: React.FunctionComponent = ({ + componentTemplateName, + history, +}) => { const { api, trackMetric } = useComponentTemplatesContext(); const { data, isLoading, error, sendRequest } = api.useLoadComponentTemplates(); const [componentTemplatesToDelete, setComponentTemplatesToDelete] = useState([]); + const goToList = () => { + return history.push('component_templates'); + }; + // Track component loaded useEffect(() => { trackMetric('loaded', UIM_COMPONENT_TEMPLATE_LIST_LOAD); @@ -49,6 +64,7 @@ export const ComponentTemplateList: React.FunctionComponent = () => { componentTemplates={data} onReloadClick={sendRequest} onDeleteClick={setComponentTemplatesToDelete} + history={history as ScopedHistory} /> ); } else if (error) { @@ -58,18 +74,44 @@ export const ComponentTemplateList: React.FunctionComponent = () => { return (
{content} + + {/* delete modal */} {componentTemplatesToDelete?.length > 0 ? ( { if (deleteResponse?.hasDeletedComponentTemplates) { // refetch the component templates sendRequest(); + // go back to list view (if deleted from details flyout) + goToList(); } setComponentTemplatesToDelete([]); }} componentTemplatesToDelete={componentTemplatesToDelete} /> ) : null} + + {/* details flyout */} + {componentTemplateName && ( + + details._kbnMeta.usedBy.length > 0, + closePopoverOnClick: true, + handleActionClick: () => { + setComponentTemplatesToDelete([componentTemplateName]); + }, + }, + ]} + /> + )}
); }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list_container.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list_container.tsx index af8ab1b94c790..d0636da8cf93a 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list_container.tsx @@ -5,16 +5,28 @@ */ import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; import { ComponentTemplatesAuthProvider } from './auth_provider'; import { ComponentTemplatesWithPrivileges } from './with_privileges'; import { ComponentTemplateList } from './component_template_list'; -export const ComponentTemplateListContainer: React.FunctionComponent = () => { +interface MatchParams { + componentTemplateName?: string; +} + +export const ComponentTemplateListContainer: React.FunctionComponent> = ({ + match: { + params: { componentTemplateName }, + }, + history, +}) => { return ( - + ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx index 2d9557e64e6e7..b67a249ae6976 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx @@ -12,21 +12,30 @@ import { EuiInMemoryTableProps, EuiTextColor, EuiIcon, + EuiLink, } from '@elastic/eui'; +import { ScopedHistory } from 'kibana/public'; -import { ComponentTemplateListItem } from '../types'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; +import { ComponentTemplateListItem } from '../shared_imports'; +import { UIM_COMPONENT_TEMPLATE_DETAILS } from '../constants'; +import { useComponentTemplatesContext } from '../component_templates_context'; export interface Props { componentTemplates: ComponentTemplateListItem[]; onReloadClick: () => void; onDeleteClick: (componentTemplateName: string[]) => void; + history: ScopedHistory; } export const ComponentTable: FunctionComponent = ({ componentTemplates, onReloadClick, onDeleteClick, + history, }) => { + const { trackMetric } = useComponentTemplatesContext(); + const [selection, setSelection] = useState([]); const tableProps: EuiInMemoryTableProps = { @@ -120,6 +129,21 @@ export const ComponentTable: FunctionComponent = ({ defaultMessage: 'Name', }), sortable: true, + render: (name: string) => ( + /* eslint-disable-next-line @elastic/eui/href-or-on-click */ + trackMetric('click', UIM_COMPONENT_TEMPLATE_DETAILS) + )} + data-test-subj="templateDetailsLink" + > + {name} + + ), }, { field: 'usedBy', diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx index 55f20ce21d417..e8116409def4b 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx @@ -14,7 +14,6 @@ const ComponentTemplatesContext = createContext(undefined); interface Props { httpClient: HttpSetup; apiBasePath: string; - appBasePath: string; trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void; docLinks: DocLinksSetup; toasts: NotificationsSetup['toasts']; @@ -27,7 +26,6 @@ interface Context { documentation: ReturnType; trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void; toasts: NotificationsSetup['toasts']; - appBasePath: string; } export const ComponentTemplatesProvider = ({ @@ -37,7 +35,7 @@ export const ComponentTemplatesProvider = ({ value: Props; children: React.ReactNode; }) => { - const { httpClient, apiBasePath, trackMetric, docLinks, toasts, appBasePath } = value; + const { httpClient, apiBasePath, trackMetric, docLinks, toasts } = value; const useRequest = getUseRequest(httpClient); const sendRequest = getSendRequest(httpClient); @@ -47,7 +45,7 @@ export const ComponentTemplatesProvider = ({ return ( {children} diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts b/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts index 501acde07fc00..e9acfa8dcc56d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts @@ -8,6 +8,7 @@ export const UIM_COMPONENT_TEMPLATE_LIST_LOAD = 'component_template_list_load'; export const UIM_COMPONENT_TEMPLATE_DELETE = 'component_template_delete'; export const UIM_COMPONENT_TEMPLATE_DELETE_MANY = 'component_template_delete_many'; +export const UIM_COMPONENT_TEMPLATE_DETAILS = 'component_template_details'; // privileges export const APP_CLUSTER_REQUIRED_PRIVILEGES = ['manage_index_templates']; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts index e0219ec71787f..72e79a57ae413 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts @@ -8,4 +8,4 @@ export { ComponentTemplatesProvider } from './component_templates_context'; export { ComponentTemplateList } from './component_template_list'; -export * from './types'; +export { ComponentTemplateDetailsFlyout } from './component_template_details'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts index 351e83c6c0cb5..4a8cf965adfb9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ComponentTemplateListItem } from '../types'; -import { UseRequestHook, SendRequestHook } from './request'; +import { ComponentTemplateListItem, ComponentTemplateDeserialized } from '../shared_imports'; import { UIM_COMPONENT_TEMPLATE_DELETE_MANY, UIM_COMPONENT_TEMPLATE_DELETE } from '../constants'; +import { UseRequestHook, SendRequestHook } from './request'; export const getApi = ( useRequest: UseRequestHook, @@ -37,8 +37,16 @@ export const getApi = ( return result; } + function useLoadComponentTemplate(name: string) { + return useRequest({ + path: `${apiBasePath}/component_templates/${encodeURIComponent(name)}`, + method: 'get', + }); + } + return { useLoadComponentTemplates, deleteComponentTemplates, + useLoadComponentTemplate, }; }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index 049204f03c0c1..4e56f4a8c9818 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -19,3 +19,11 @@ export { useAuthorizationContext, NotAuthorizedSection, } from '../../../../../../../src/plugins/es_ui_shared/public'; + +export { TabMappings, TabSettings, TabAliases } from '../shared'; + +export { + ComponentTemplateSerialized, + ComponentTemplateDeserialized, + ComponentTemplateListItem, +} from '../../../../common'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/types.ts b/x-pack/plugins/index_management/public/application/components/component_templates/types.ts deleted file mode 100644 index 0aab3b6b0a94a..0000000000000 --- a/x-pack/plugins/index_management/public/application/components/component_templates/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// Ideally, we shouldn't depend on anything in index management that is -// outside of the components_templates directory -// We could consider creating shared types or duplicating the types here if -// the component_templates app were to move outside of index management -import { - ComponentTemplateSerialized, - ComponentTemplateDeserialized, - ComponentTemplateListItem, -} from '../../../../common'; - -export { ComponentTemplateSerialized, ComponentTemplateDeserialized, ComponentTemplateListItem }; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/index.ts new file mode 100644 index 0000000000000..af5cd500a286a --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/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 { TabAliases } from './tab_aliases'; +export { TabMappings } from './tab_mappings'; +export { TabSettings } from './tab_settings'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_aliases.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_aliases.tsx new file mode 100644 index 0000000000000..bcf59fd13bad6 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_aliases.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; + +import { Aliases } from '../../../../../../common'; + +interface Props { + aliases: Aliases | undefined; +} + +export const TabAliases: React.FunctionComponent = ({ aliases }) => { + if (aliases && Object.keys(aliases).length) { + return ( +
+ {JSON.stringify(aliases, null, 2)} +
+ ); + } + + return ( + + } + iconType="pin" + data-test-subj="noAliasesCallout" + size="s" + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_mappings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_mappings.tsx new file mode 100644 index 0000000000000..3c3d88345e5a1 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_mappings.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; +import { Mappings } from '../../../../../../common'; + +interface Props { + mappings: Mappings | undefined; +} + +export const TabMappings: React.FunctionComponent = ({ mappings }) => { + if (mappings && Object.keys(mappings).length) { + return ( +
+ {JSON.stringify(mappings, null, 2)} +
+ ); + } + + return ( + + } + iconType="pin" + data-test-subj="noMappingsCallout" + size="s" + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_settings.tsx new file mode 100644 index 0000000000000..6d5cf09588a1c --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/details_panel/tab_settings.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; +import { IndexSettings } from '../../../../../../common'; + +interface Props { + settings: IndexSettings | undefined; +} + +export const TabSettings: React.FunctionComponent = ({ settings }) => { + if (settings && Object.keys(settings).length) { + return ( +
+ {JSON.stringify(settings, null, 2)} +
+ ); + } + + return ( + + } + iconType="pin" + data-test-subj="noSettingsCallout" + size="s" + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts new file mode 100644 index 0000000000000..90d66bd1a5da4 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TabAliases, TabMappings, TabSettings } from './details_panel'; diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts new file mode 100644 index 0000000000000..e015ef72e244f --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/shared/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 { TabAliases, TabMappings, TabSettings } from './components'; diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index 5d1096c9ee24e..ff54b4b1bfe35 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -10,7 +10,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { CoreStart } from '../../../../../src/core/public'; -import { API_BASE_PATH, BASE_PATH } from '../../common'; +import { API_BASE_PATH } from '../../common'; import { AppContextProvider, AppDependencies } from './app_context'; import { App } from './app'; @@ -32,7 +32,6 @@ export const renderApp = ( const componentTemplateProviderValues = { httpClient: services.httpService.httpClient, apiBasePath: API_BASE_PATH, - appBasePath: BASE_PATH, trackMetric: services.uiMetricService.trackMetric.bind(services.uiMetricService), docLinks, toasts: notifications.toasts, diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 51deaf42cc72c..7bd04cdbf0c91 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -150,7 +150,14 @@ export const IndexManagementHome: React.FunctionComponent - + diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index 14c53557ba2c7..78b7f86993cbd 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; @@ -32,6 +32,8 @@ export const LogsPageContent: React.FunctionComponent = () => { const { initialize } = useLogSourceContext(); + const kibana = useKibana(); + useMount(() => { initialize(); }); @@ -88,6 +90,16 @@ export const LogsPageContent: React.FunctionComponent = () => { + + + {ADD_DATA_LABEL} + + @@ -123,3 +135,7 @@ const settingsTabTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle }); const feedbackLinkUrl = 'https://discuss.elastic.co/c/logs'; + +const ADD_DATA_LABEL = i18n.translate('xpack.infra.logsHeaderAddDataButtonLabel', { + defaultMessage: 'Add data', +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 35a6cadc786f6..05296fbf6b0a3 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { EuiErrorBoundary, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { EuiErrorBoundary, EuiFlexItem, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { RoutedTabs } from '../../components/navigation/routed_tabs'; @@ -32,9 +32,15 @@ import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters import { InventoryAlertDropdown } from '../../components/alerting/inventory/alert_dropdown'; import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown'; +const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { + defaultMessage: 'Add data', +}); + export const InfrastructurePage = ({ match }: RouteComponentProps) => { const uiCapabilities = useKibana().services.application?.capabilities; + const kibana = useKibana(); + return ( @@ -102,6 +108,18 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { + + + {ADD_DATA_LABEL} + + diff --git a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts index 9dfd76b9ddd21..a3bef72e8db5a 100644 --- a/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts +++ b/x-pack/plugins/ingest_manager/common/services/config_to_yaml.ts @@ -11,10 +11,11 @@ const CONFIG_KEYS_ORDER = [ 'name', 'revision', 'type', - 'outputs', 'settings', - 'datasources', + 'outputs', + 'inputs', 'enabled', + 'use_output', 'package', 'input', ]; diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts deleted file mode 100644 index d319ba2beddf9..0000000000000 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { Datasource, DatasourceInput } from '../types'; -import { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; - -describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { - const mockDatasource: Datasource = { - id: 'some-uuid', - name: 'mock-datasource', - description: '', - created_at: '', - created_by: '', - updated_at: '', - updated_by: '', - config_id: '', - enabled: true, - output_id: '', - namespace: 'default', - inputs: [], - revision: 1, - }; - - const mockInput: DatasourceInput = { - type: 'test-logs', - enabled: true, - vars: { - inputVar: { value: 'input-value' }, - inputVar2: { value: undefined }, - inputVar3: { - type: 'yaml', - value: 'testField: test', - }, - inputVar4: { value: '' }, - }, - streams: [ - { - id: 'test-logs-foo', - enabled: true, - dataset: 'foo', - vars: { - fooVar: { value: 'foo-value' }, - fooVar2: { value: [1, 2] }, - }, - agent_stream: { - fooKey: 'fooValue1', - fooKey2: ['fooValue2'], - }, - }, - { - id: 'test-logs-bar', - enabled: true, - dataset: 'bar', - vars: { - barVar: { value: 'bar-value' }, - barVar2: { value: [1, 2] }, - barVar3: { - type: 'yaml', - value: - '- namespace: mockNamespace\n #disabledProp: ["test"]\n anotherProp: test\n- namespace: mockNamespace2\n #disabledProp: ["test2"]\n anotherProp: test2', - }, - barVar4: { - type: 'yaml', - value: '', - }, - barVar5: { - type: 'yaml', - value: 'testField: test\n invalidSpacing: foo', - }, - }, - }, - ], - }; - - it('returns agent datasource config for datasource with no inputs', () => { - expect(storedDatasourceToAgentDatasource(mockDatasource)).toEqual({ - id: 'some-uuid', - name: 'mock-datasource', - namespace: 'default', - enabled: true, - use_output: 'default', - inputs: [], - }); - - expect( - storedDatasourceToAgentDatasource({ - ...mockDatasource, - package: { - name: 'mock-package', - title: 'Mock package', - version: '0.0.0', - }, - }) - ).toEqual({ - id: 'some-uuid', - name: 'mock-datasource', - namespace: 'default', - enabled: true, - use_output: 'default', - package: { - name: 'mock-package', - version: '0.0.0', - }, - inputs: [], - }); - }); - - it('returns agent datasource config with flattened input and package stream', () => { - expect(storedDatasourceToAgentDatasource({ ...mockDatasource, inputs: [mockInput] })).toEqual({ - id: 'some-uuid', - name: 'mock-datasource', - namespace: 'default', - enabled: true, - use_output: 'default', - inputs: [ - { - type: 'test-logs', - enabled: true, - streams: [ - { - id: 'test-logs-foo', - enabled: true, - dataset: 'foo', - fooKey: 'fooValue1', - fooKey2: ['fooValue2'], - }, - { - id: 'test-logs-bar', - enabled: true, - dataset: 'bar', - }, - ], - }, - ], - }); - }); - - it('returns agent datasource config without disabled streams', () => { - expect( - storedDatasourceToAgentDatasource({ - ...mockDatasource, - inputs: [ - { - ...mockInput, - streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], - }, - ], - }) - ).toEqual({ - id: 'some-uuid', - name: 'mock-datasource', - namespace: 'default', - enabled: true, - use_output: 'default', - inputs: [ - { - type: 'test-logs', - enabled: true, - streams: [ - { - id: 'test-logs-foo', - enabled: true, - dataset: 'foo', - fooKey: 'fooValue1', - fooKey2: ['fooValue2'], - }, - ], - }, - ], - }); - }); - - it('returns agent datasource config without disabled inputs', () => { - expect( - storedDatasourceToAgentDatasource({ - ...mockDatasource, - inputs: [{ ...mockInput, enabled: false }], - }) - ).toEqual({ - id: 'some-uuid', - name: 'mock-datasource', - namespace: 'default', - enabled: true, - use_output: 'default', - inputs: [], - }); - }); -}); diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts deleted file mode 100644 index 2a8b687675bf9..0000000000000 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { Datasource, FullAgentConfigDatasource } from '../types'; -import { DEFAULT_OUTPUT } from '../constants'; - -export const storedDatasourceToAgentDatasource = ( - datasource: Datasource -): FullAgentConfigDatasource => { - const { id, name, namespace, enabled, package: pkg, inputs } = datasource; - - const fullDatasource: FullAgentConfigDatasource = { - id: id || name, - name, - namespace, - enabled, - use_output: DEFAULT_OUTPUT.name, // TODO: hardcoded to default output for now - inputs: inputs - .filter((input) => input.enabled) - .map((input) => { - const fullInput = { - ...input, - ...Object.entries(input.config || {}).reduce((acc, [key, { value }]) => { - acc[key] = value; - return acc; - }, {} as { [k: string]: any }), - streams: input.streams - .filter((stream) => stream.enabled) - .map((stream) => { - const fullStream = { - ...stream, - ...stream.agent_stream, - ...Object.entries(stream.config || {}).reduce((acc, [key, { value }]) => { - acc[key] = value; - return acc; - }, {} as { [k: string]: any }), - }; - delete fullStream.agent_stream; - delete fullStream.vars; - delete fullStream.config; - return fullStream; - }), - }; - delete fullInput.vars; - delete fullInput.config; - return fullInput; - }), - }; - - if (pkg) { - fullDatasource.package = { - name: pkg.name, - version: pkg.version, - }; - } - - return fullDatasource; -}; diff --git a/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.test.ts b/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.test.ts new file mode 100644 index 0000000000000..df94168ec88d0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.test.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Datasource, DatasourceInput } from '../types'; +import { storedDatasourcesToAgentInputs } from './datasources_to_agent_inputs'; + +describe('Ingest Manager - storedDatasourcesToAgentInputs', () => { + const mockDatasource: Datasource = { + id: 'some-uuid', + name: 'mock-datasource', + description: '', + created_at: '', + created_by: '', + updated_at: '', + updated_by: '', + config_id: '', + enabled: true, + output_id: '', + namespace: 'default', + inputs: [], + revision: 1, + }; + + const mockInput: DatasourceInput = { + type: 'test-logs', + enabled: true, + vars: { + inputVar: { value: 'input-value' }, + inputVar2: { value: undefined }, + inputVar3: { + type: 'yaml', + value: 'testField: test', + }, + inputVar4: { value: '' }, + }, + streams: [ + { + id: 'test-logs-foo', + enabled: true, + dataset: 'foo', + vars: { + fooVar: { value: 'foo-value' }, + fooVar2: { value: [1, 2] }, + }, + agent_stream: { + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + }, + { + id: 'test-logs-bar', + enabled: true, + dataset: 'bar', + vars: { + barVar: { value: 'bar-value' }, + barVar2: { value: [1, 2] }, + barVar3: { + type: 'yaml', + value: + '- namespace: mockNamespace\n #disabledProp: ["test"]\n anotherProp: test\n- namespace: mockNamespace2\n #disabledProp: ["test2"]\n anotherProp: test2', + }, + barVar4: { + type: 'yaml', + value: '', + }, + barVar5: { + type: 'yaml', + value: 'testField: test\n invalidSpacing: foo', + }, + }, + }, + ], + }; + + it('returns no inputs for datasource with no inputs, or only disabled inputs', () => { + expect(storedDatasourcesToAgentInputs([mockDatasource])).toEqual([]); + + expect( + storedDatasourcesToAgentInputs([ + { + ...mockDatasource, + package: { + name: 'mock-package', + title: 'Mock package', + version: '0.0.0', + }, + }, + ]) + ).toEqual([]); + + expect( + storedDatasourcesToAgentInputs([ + { + ...mockDatasource, + inputs: [{ ...mockInput, enabled: false }], + }, + ]) + ).toEqual([]); + }); + + it('returns agent inputs', () => { + expect(storedDatasourcesToAgentInputs([{ ...mockDatasource, inputs: [mockInput] }])).toEqual([ + { + id: 'some-uuid', + name: 'mock-datasource', + type: 'test-logs', + dataset: { namespace: 'default' }, + use_output: 'default', + streams: [ + { + id: 'test-logs-foo', + dataset: { name: 'foo' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + { + id: 'test-logs-bar', + dataset: { name: 'bar' }, + }, + ], + }, + ]); + }); + + it('returns agent inputs without disabled streams', () => { + expect( + storedDatasourcesToAgentInputs([ + { + ...mockDatasource, + inputs: [ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ], + }, + ]) + ).toEqual([ + { + id: 'some-uuid', + name: 'mock-datasource', + type: 'test-logs', + dataset: { namespace: 'default' }, + use_output: 'default', + streams: [ + { + id: 'test-logs-foo', + dataset: { name: 'foo' }, + fooKey: 'fooValue1', + fooKey2: ['fooValue2'], + }, + ], + }, + ]); + }); +}); diff --git a/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.ts b/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.ts new file mode 100644 index 0000000000000..d5a752e817b4f --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/services/datasources_to_agent_inputs.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 { Datasource, FullAgentConfigInput, FullAgentConfigInputStream } from '../types'; +import { DEFAULT_OUTPUT } from '../constants'; + +export const storedDatasourcesToAgentInputs = ( + datasources: Datasource[] +): FullAgentConfigInput[] => { + const fullInputs: FullAgentConfigInput[] = []; + + datasources.forEach((datasource) => { + if (!datasource.enabled || !datasource.inputs || !datasource.inputs.length) { + return; + } + datasource.inputs.forEach((input) => { + if (!input.enabled) { + return; + } + + const fullInput: FullAgentConfigInput = { + id: datasource.id || datasource.name, + name: datasource.name, + type: input.type, + dataset: { namespace: datasource.namespace || 'default' }, + use_output: DEFAULT_OUTPUT.name, + ...Object.entries(input.config || {}).reduce((acc, [key, { value }]) => { + acc[key] = value; + return acc; + }, {} as { [k: string]: any }), + streams: input.streams + .filter((stream) => stream.enabled) + .map((stream) => { + const fullStream: FullAgentConfigInputStream = { + id: stream.id, + dataset: { name: stream.dataset }, + ...stream.agent_stream, + ...Object.entries(stream.config || {}).reduce((acc, [key, { value }]) => { + acc[key] = value; + return acc; + }, {} as { [k: string]: any }), + }; + if (stream.processors) { + fullStream.processors = stream.processors; + } + return fullStream; + }), + }; + + if (datasource.package) { + fullInput.package = { + name: datasource.package.name, + version: datasource.package.version, + }; + } + + fullInputs.push(fullInput); + }); + }); + + return fullInputs; +}; diff --git a/x-pack/plugins/ingest_manager/common/services/index.ts b/x-pack/plugins/ingest_manager/common/services/index.ts index c595c9a52f66f..e53d97972fa2f 100644 --- a/x-pack/plugins/ingest_manager/common/services/index.ts +++ b/x-pack/plugins/ingest_manager/common/services/index.ts @@ -7,7 +7,7 @@ import * as AgentStatusKueryHelper from './agent_status'; export * from './routes'; export { packageToConfigDatasourceInputs, packageToConfigDatasource } from './package_to_config'; -export { storedDatasourceToAgentDatasource } from './datasource_to_agent_datasource'; +export { storedDatasourcesToAgentInputs } from './datasources_to_agent_inputs'; export { configToYaml } from './config_to_yaml'; export { AgentStatusKueryHelper }; export { decodeCloudId } from './decode_cloud_id'; diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts index 7547f56237eec..36b3176ffa415 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent_config.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent_config.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 { - Datasource, - DatasourcePackage, - DatasourceInput, - DatasourceInputStream, -} from './datasource'; +import { Datasource, DatasourcePackage, DatasourceInputStream } from './datasource'; import { Output } from './output'; export enum AgentConfigStatus { @@ -35,23 +30,22 @@ export interface AgentConfig extends NewAgentConfig { export type AgentConfigSOAttributes = Omit; -export type FullAgentConfigDatasource = Pick< - Datasource, - 'id' | 'name' | 'namespace' | 'enabled' -> & { - package?: Pick; - use_output: string; - inputs: Array< - Omit & { - streams: Array< - Omit & { - [key: string]: any; - } - >; - } - >; +export type FullAgentConfigInputStream = Pick & { + dataset: { name: string }; + [key: string]: any; }; +export interface FullAgentConfigInput { + id: string; + name: string; + type: string; + dataset: { namespace: string }; + use_output: string; + package?: Pick; + streams: FullAgentConfigInputStream[]; + [key: string]: any; +} + export interface FullAgentConfig { id: string; outputs: { @@ -59,7 +53,7 @@ export interface FullAgentConfig { [key: string]: any; }; }; - datasources: FullAgentConfigDatasource[]; + inputs: FullAgentConfigInput[]; revision?: number; settings?: { monitoring: { diff --git a/x-pack/plugins/ingest_manager/kibana.json b/x-pack/plugins/ingest_manager/kibana.json index 382ea0444093d..35447139607a6 100644 --- a/x-pack/plugins/ingest_manager/kibana.json +++ b/x-pack/plugins/ingest_manager/kibana.json @@ -5,5 +5,6 @@ "ui": true, "configPath": ["xpack", "ingestManager"], "requiredPlugins": ["licensing", "data", "encryptedSavedObjects"], - "optionalPlugins": ["security", "features", "cloud"] + "optionalPlugins": ["security", "features", "cloud"], + "extraPublicDirs": ["common"] } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx index 9354d94f46801..e0623108e7d39 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/header.tsx @@ -36,6 +36,7 @@ export interface HeaderProps { rightColumn?: JSX.Element; rightColumnGrow?: EuiFlexItemProps['grow']; tabs?: EuiTabProps[]; + 'data-test-subj'?: string; } const HeaderColumns: React.FC> = memo( @@ -53,8 +54,9 @@ export const Header: React.FC = ({ rightColumnGrow, tabs, maxWidth, + 'data-test-subj': dataTestSubj, }) => ( - + { + forRoute: string; + routeState?: S; +} + +const IntraAppStateContext = React.createContext({ forRoute: '' }); +const wasHandled = new WeakSet(); + +/** + * Provides a bridget between Kibana's ScopedHistory instance (normally used with BrowserRouter) + * and the Hash router used within the app in order to enable state to be used between kibana + * apps + */ +export const IntraAppStateProvider = memo<{ + kibanaScopedHistory: AppMountParameters['history']; + children: React.ReactNode; +}>(({ kibanaScopedHistory, children }) => { + const internalAppToAppState = useMemo(() => { + return { + forRoute: kibanaScopedHistory.location.hash.substr(1), + routeState: kibanaScopedHistory.location.state as AnyIntraAppRouteState, + }; + }, [kibanaScopedHistory.location.hash, kibanaScopedHistory.location.state]); + return ( + + {children} + + ); +}); + +/** + * Retrieve UI Route state from the React Router History for the current URL location. + * This state can be used by other Kibana Apps to influence certain behaviours in Ingest, for example, + * redirecting back to an given Application after a craete action. + */ +export function useIntraAppState(): + | IntraAppState['routeState'] + | undefined { + const location = useLocation(); + const intraAppState = useContext(IntraAppStateContext); + if (!intraAppState) { + throw new Error('Hook called outside of IntraAppStateContext'); + } + return useMemo(() => { + // Due to the use of HashRouter in Ingest, we only want state to be returned + // once so that it does not impact navigation to the page from within the + // ingest app. side affect is that the browser back button would not work + // consistently either. + if (location.pathname === intraAppState.forRoute && !wasHandled.has(intraAppState)) { + wasHandled.add(intraAppState); + return intraAppState.routeState as S; + } + }, [intraAppState, location.pathname]); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx index ed5a75ce6c991..623df428b7dd9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -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 React, { useEffect, useState } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { useObservable } from 'react-use'; import { HashRouter as Router, Redirect, Switch, Route, RouteProps } from 'react-router-dom'; @@ -28,6 +28,7 @@ import { useCore, sendSetup, sendGetPermissionsCheck } from './hooks'; import { FleetStatusProvider } from './hooks/use_fleet_status'; import './index.scss'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { IntraAppStateProvider } from './hooks/use_intra_app_state'; export interface ProtectedRouteProps extends RouteProps { isAllowed?: boolean; @@ -56,163 +57,170 @@ const ErrorLayout = ({ children }: { children: JSX.Element }) => ( ); -const IngestManagerRoutes = ({ ...rest }) => { - const { epm, fleet } = useConfig(); - const { notifications } = useCore(); +const IngestManagerRoutes = memo<{ history: AppMountParameters['history']; basepath: string }>( + ({ history, ...rest }) => { + const { epm, fleet } = useConfig(); + const { notifications } = useCore(); - const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); - const [permissionsError, setPermissionsError] = useState(); - const [isInitialized, setIsInitialized] = useState(false); - const [initializationError, setInitializationError] = useState(null); + const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); + const [permissionsError, setPermissionsError] = useState(); + const [isInitialized, setIsInitialized] = useState(false); + const [initializationError, setInitializationError] = useState(null); - useEffect(() => { - (async () => { - setIsPermissionsLoading(false); - setPermissionsError(undefined); - setIsInitialized(false); - setInitializationError(null); - try { - setIsPermissionsLoading(true); - const permissionsResponse = await sendGetPermissionsCheck(); + useEffect(() => { + (async () => { setIsPermissionsLoading(false); - if (permissionsResponse.data?.success) { - try { - const setupResponse = await sendSetup(); - if (setupResponse.error) { - setInitializationError(setupResponse.error); + setPermissionsError(undefined); + setIsInitialized(false); + setInitializationError(null); + try { + setIsPermissionsLoading(true); + const permissionsResponse = await sendGetPermissionsCheck(); + setIsPermissionsLoading(false); + if (permissionsResponse.data?.success) { + try { + const setupResponse = await sendSetup(); + if (setupResponse.error) { + setInitializationError(setupResponse.error); + } + } catch (err) { + setInitializationError(err); } - } catch (err) { - setInitializationError(err); + setIsInitialized(true); + } else { + setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR'); } - setIsInitialized(true); - } else { - setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR'); + } catch (err) { + setPermissionsError('REQUEST_ERROR'); } - } catch (err) { - setPermissionsError('REQUEST_ERROR'); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - if (isPermissionsLoading || permissionsError) { - return ( - - {isPermissionsLoading ? ( - - ) : permissionsError === 'REQUEST_ERROR' ? ( - - } - error={i18n.translate('xpack.ingestManager.permissionsRequestErrorMessageDescription', { - defaultMessage: 'There was a problem checking Ingest Manager permissions', - })} - /> - ) : ( - - + {isPermissionsLoading ? ( + + ) : permissionsError === 'REQUEST_ERROR' ? ( + - {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( - - ) : ( - - )} - + } - body={ -

- {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( - superuser }} - /> - ) : ( - - )} -

+ error={i18n.translate( + 'xpack.ingestManager.permissionsRequestErrorMessageDescription', + { + defaultMessage: 'There was a problem checking Ingest Manager permissions', + } + )} + /> + ) : ( + + + {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( + + ) : ( + + )} + + } + body={ +

+ {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( + superuser }} + /> + ) : ( + + )} +

+ } + /> +
+ )} +
+ ); + } + + if (!isInitialized || initializationError) { + return ( + + {initializationError ? ( + } + error={initializationError} /> - - )} - - ); - } + ) : ( + + )} + + ); + } - if (!isInitialized || initializationError) { return ( - - {initializationError ? ( - - } - error={initializationError} - /> - ) : ( - - )} - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; +); const IngestManagerApp = ({ basepath, @@ -220,12 +228,14 @@ const IngestManagerApp = ({ setupDeps, startDeps, config, + history, }: { basepath: string; coreStart: CoreStart; setupDeps: IngestManagerSetupDeps; startDeps: IngestManagerStartDeps; config: IngestManagerConfigType; + history: AppMountParameters['history']; }) => { const isDarkMode = useObservable(coreStart.uiSettings.get$('theme:darkMode')); return ( @@ -234,7 +244,7 @@ const IngestManagerApp = ({ - + @@ -245,7 +255,7 @@ const IngestManagerApp = ({ export function renderApp( coreStart: CoreStart, - { element, appBasePath }: AppMountParameters, + { element, appBasePath, history }: AppMountParameters, setupDeps: IngestManagerSetupDeps, startDeps: IngestManagerStartDeps, config: IngestManagerConfigType @@ -258,6 +268,7 @@ export function renderApp( setupDeps={setupDeps} startDeps={startDeps} config={config} + history={history} />, element ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx index ac7f85bf5f594..58ca989850bf1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/with_header.tsx @@ -15,6 +15,7 @@ const Page = styled(EuiPage)` interface Props extends HeaderProps { restrictWidth?: number; restrictHeaderWidth?: number; + 'data-test-subj'?: string; children?: React.ReactNode; } @@ -22,11 +23,19 @@ export const WithHeaderLayout: React.FC = ({ restrictWidth, restrictHeaderWidth, children, + 'data-test-subj': dataTestSubj, ...rest }) => ( -
- +
+ {children} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx index ccd2bc75fe223..7939feed80143 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx @@ -23,13 +23,31 @@ import { CreateDatasourceFrom } from '../types'; export const CreateDatasourcePageLayout: React.FunctionComponent<{ from: CreateDatasourceFrom; cancelUrl: string; + cancelOnClick?: React.ReactEventHandler; agentConfig?: AgentConfig; packageInfo?: PackageInfo; -}> = ({ from, cancelUrl, agentConfig, packageInfo, children }) => { + 'data-test-subj'?: string; +}> = ({ + from, + cancelUrl, + cancelOnClick, + agentConfig, + packageInfo, + children, + 'data-test-subj': dataTestSubj, +}) => { const leftColumn = ( - + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + {children} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx index 577f08cdc3313..876383770aa71 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx @@ -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 React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useState, useEffect, useMemo, useCallback, ReactEventHandler } from 'react'; import { useRouteMatch, useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -17,7 +17,12 @@ import { EuiSpacer, } from '@elastic/eui'; import { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import { AgentConfig, PackageInfo, NewDatasource } from '../../../types'; +import { + AgentConfig, + PackageInfo, + NewDatasource, + CreateDatasourceRouteState, +} from '../../../types'; import { useLink, useBreadcrumbs, @@ -34,12 +39,14 @@ import { StepSelectPackage } from './step_select_package'; import { StepSelectConfig } from './step_select_config'; import { StepConfigureDatasource } from './step_configure_datasource'; import { StepDefineDatasource } from './step_define_datasource'; +import { useIntraAppState } from '../../../hooks/use_intra_app_state'; export const CreateDatasourcePage: React.FunctionComponent = () => { const { notifications, chrome: { getIsNavDrawerLocked$ }, uiSettings, + application: { navigateToApp }, } = useCore(); const { fleet: { enabled: isFleetEnabled }, @@ -49,6 +56,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { } = useRouteMatch(); const { getHref, getPath } = useLink(); const history = useHistory(); + const routeState = useIntraAppState(); const from: CreateDatasourceFrom = configId ? 'config' : 'package'; const [isNavDrawerLocked, setIsNavDrawerLocked] = useState(false); @@ -171,10 +179,24 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { }; // Cancel path - const cancelUrl = - from === 'config' - ? getHref('configuration_details', { configId: agentConfig?.id || configId }) + const cancelUrl = useMemo(() => { + if (routeState && routeState.onCancelUrl) { + return routeState.onCancelUrl; + } + return from === 'config' + ? getHref('configuration_details', { configId: agentConfigId || configId }) : getHref('integration_details', { pkgkey }); + }, [agentConfigId, configId, from, getHref, pkgkey, routeState]); + + const cancelClickHandler: ReactEventHandler = useCallback( + (ev) => { + if (routeState && routeState.onCancelNavigateTo) { + ev.preventDefault(); + navigateToApp(...routeState.onCancelNavigateTo); + } + }, + [routeState, navigateToApp] + ); // Save datasource const saveDatasource = async () => { @@ -193,9 +215,18 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { setFormState('CONFIRM'); return; } - const { error } = await saveDatasource(); + const { error, data } = await saveDatasource(); if (!error) { - history.push(getPath('configuration_details', { configId: agentConfig?.id || configId })); + if (routeState && routeState.onSaveNavigateTo) { + navigateToApp( + ...(typeof routeState.onSaveNavigateTo === 'function' + ? routeState.onSaveNavigateTo(data!.item) + : routeState.onSaveNavigateTo) + ); + } else { + history.push(getPath('configuration_details', { configId: agentConfig?.id || configId })); + } + notifications.toasts.addSuccess({ title: i18n.translate('xpack.ingestManager.createDatasource.addedNotificationTitle', { defaultMessage: `Successfully added '{datasourceName}'`, @@ -212,6 +243,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { }, }) : undefined, + 'data-test-subj': 'datasourceCreateSuccessToast', }); } else { notifications.toasts.addError(error, { @@ -224,6 +256,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { const layoutProps = { from, cancelUrl, + cancelOnClick: cancelClickHandler, agentConfig, packageInfo, }; @@ -287,6 +320,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { defaultMessage: 'Select the data you want to collect', }), status: !packageInfo || !agentConfig ? 'disabled' : undefined, + 'data-test-subj': 'dataCollectionSetupStep', children: agentConfig && packageInfo ? ( { ]; return ( - + {formState === 'CONFIRM' && agentConfig && ( { > - + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { iconType="save" color="primary" fill + data-test-subj="createDatasourceSaveButton" > diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx index 22cb219f911f6..5f556a46e518d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_select_config.tsx @@ -113,6 +113,7 @@ export const StepSelectConfig: React.FunctionComponent<{ label: name, key: id, checked: selectedConfigId === id ? 'on' : undefined, + 'data-test-subj': 'agentConfigItem', }; })} renderOption={(option) => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 6dbc8d67caaee..ece7aef2c247f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -19,7 +19,7 @@ export { settingsRoutesService, appRoutesService, packageToConfigDatasourceInputs, - storedDatasourceToAgentDatasource, + storedDatasourcesToAgentInputs, configToYaml, AgentStatusKueryHelper, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 412bf412d1ef5..1a4c6a8a86f6e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -99,3 +99,5 @@ export { InstallationStatus, Installable, } from '../../../../common'; + +export * from './intra_app_route_state'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts new file mode 100644 index 0000000000000..6e85d12f71891 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.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 { ApplicationStart } from 'kibana/public'; +import { Datasource } from '../../../../common/types/models'; + +/** + * Supported routing state for the create datasource page routes + */ +export interface CreateDatasourceRouteState { + /** On a successful save of the datasource, use navigate to the given app */ + onSaveNavigateTo?: + | Parameters + | ((newDatasource: Datasource) => Parameters); + /** On cancel, navigate to the given app */ + onCancelNavigateTo?: Parameters; + /** Url to be used on cancel links */ + onCancelUrl?: string; +} + +/** + * All possible Route states. + */ +export type AnyIntraAppRouteState = CreateDatasourceRouteState; diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/ingest_manager/public/index.ts index e26f310b6d9c6..9f4893ac6e499 100644 --- a/x-pack/plugins/ingest_manager/public/index.ts +++ b/x-pack/plugins/ingest_manager/public/index.ts @@ -19,3 +19,4 @@ export { } from './applications/ingest_manager/sections/agent_config/create_datasource_page/components/custom_configure_datasource'; export { NewDatasource } from './applications/ingest_manager/types'; +export * from './applications/ingest_manager/types/intra_app_route_state'; diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.test.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.test.ts index 17758f6e3d7f1..c46e648ad088a 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.test.ts @@ -59,7 +59,7 @@ describe('agent config', () => { api_key: undefined, }, }, - datasources: [], + inputs: [], revision: 1, settings: { monitoring: { @@ -88,7 +88,7 @@ describe('agent config', () => { api_key: undefined, }, }, - datasources: [], + inputs: [], revision: 1, settings: { monitoring: { @@ -118,7 +118,7 @@ describe('agent config', () => { api_key: undefined, }, }, - datasources: [], + inputs: [], revision: 1, settings: { monitoring: { diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index 4a877ef7de13b..9e0386de74763 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -20,7 +20,7 @@ import { AgentConfigStatus, ListWithKuery, } from '../types'; -import { DeleteAgentConfigResponse, storedDatasourceToAgentDatasource } from '../../common'; +import { DeleteAgentConfigResponse, storedDatasourcesToAgentInputs } from '../../common'; import { listAgents } from './agents'; import { datasourceService } from './datasource'; import { outputService } from './output'; @@ -73,7 +73,8 @@ class AgentConfigService { public async ensureDefaultAgentConfig(soClient: SavedObjectsClientContract) { const configs = await soClient.find({ type: AGENT_CONFIG_SAVED_OBJECT_TYPE, - filter: `${AGENT_CONFIG_SAVED_OBJECT_TYPE}.attributes.is_default:true`, + searchFields: ['is_default'], + search: 'true', }); if (configs.total === 0) { @@ -374,9 +375,7 @@ class AgentConfigService { {} as FullAgentConfig['outputs'] ), }, - datasources: (config.datasources as Datasource[]) - .filter((datasource) => datasource.enabled) - .map((ds) => storedDatasourceToAgentDatasource(ds)), + inputs: storedDatasourcesToAgentInputs(config.datasources as Datasource[]), revision: config.revision, ...(config.monitoring_enabled && config.monitoring_enabled.length > 0 ? { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/ingest_manager/server/services/agents/status.ts index 63388db890ea5..0efb202eff532 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/status.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/status.ts @@ -56,9 +56,8 @@ export async function getAgentStatusForConfig( async function getEventsCount(soClient: SavedObjectsClientContract, configId?: string) { const { total } = await soClient.find({ type: AGENT_EVENT_SAVED_OBJECT_TYPE, - filter: configId - ? `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.config_id:"${configId}"` - : undefined, + searchFields: ['config_id'], + search: configId, perPage: 0, page: 1, sortField: 'timestamp', diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 2218d967fa8aa..2b543490ca8da 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -20,7 +20,7 @@ export { Datasource, NewDatasource, DatasourceSOAttributes, - FullAgentConfigDatasource, + FullAgentConfigInput, FullAgentConfig, AgentConfig, AgentConfigSOAttributes, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx index 6acb6369e2e90..4318062491df8 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/ingest_pipelines_create.test.tsx @@ -68,21 +68,6 @@ describe('', () => { expect(exists('versionField')).toBe(true); }); - test('should toggle the on-failure processors editor', async () => { - const { actions, component, exists } = testBed; - - // On-failure editor should be hidden by default - expect(exists('onFailureEditor')).toBe(false); - - await act(async () => { - actions.toggleOnFailureSwitch(); - await nextTick(); - component.update(); - }); - - expect(exists('onFailureEditor')).toBe(true); - }); - test('should show the request flyout', async () => { const { actions, component, find, exists } = testBed; diff --git a/x-pack/plugins/ingest_pipelines/common/types.ts b/x-pack/plugins/ingest_pipelines/common/types.ts index 8d77359a7c3c5..ad6d1cc8aa928 100644 --- a/x-pack/plugins/ingest_pipelines/common/types.ts +++ b/x-pack/plugins/ingest_pipelines/common/types.ts @@ -4,10 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Processor { - [key: string]: { - [key: string]: unknown; - }; +export interface ESProcessorConfig { + on_failure?: Processor[]; + ignore_failure?: boolean; + if?: string; + tag?: string; + [key: string]: any; +} + +export interface Processor { + [typeName: string]: ESProcessorConfig; } export interface Pipeline { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index 73bc1cfaa2cf5..ec065a74abca0 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useCallback, useRef } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; @@ -16,6 +16,11 @@ import { PipelineTestFlyout } from './pipeline_test_flyout'; import { PipelineFormFields } from './pipeline_form_fields'; import { PipelineFormError } from './pipeline_form_error'; import { pipelineFormSchema } from './schema'; +import { + OnUpdateHandlerArg, + OnUpdateHandler, + SerializeResult, +} from '../pipeline_processors_editor'; export interface PipelineFormProps { onSave: (pipeline: Pipeline) => void; @@ -30,8 +35,8 @@ export const PipelineForm: React.FunctionComponent = ({ defaultValue = { name: '', description: '', - processors: '', - on_failure: '', + processors: [], + on_failure: [], version: '', }, onSave, @@ -44,10 +49,25 @@ export const PipelineForm: React.FunctionComponent = ({ const [isTestingPipeline, setIsTestingPipeline] = useState(false); + const processorStateRef = useRef(); + const handleSave: FormConfig['onSubmit'] = async (formData, isValid) => { - if (isValid) { - onSave(formData as Pipeline); + let override: SerializeResult | undefined; + + if (!isValid) { + return; + } + + if (processorStateRef.current) { + const processorsState = processorStateRef.current; + if (await processorsState.validate()) { + override = processorsState.getData(); + } else { + return; + } } + + onSave({ ...formData, ...(override || {}) } as Pipeline); }; const handleTestPipelineClick = () => { @@ -60,6 +80,10 @@ export const PipelineForm: React.FunctionComponent = ({ onSubmit: handleSave, }); + const onEditorFlyoutOpen = useCallback(() => { + setIsRequestVisible(false); + }, [setIsRequestVisible]); + const saveButtonLabel = isSaving ? ( = ({ /> ); + const onProcessorsChangeHandler = useCallback( + (arg) => (processorStateRef.current = arg), + [] + ); + return ( <>
= ({ {/* All form fields */} @@ -147,6 +179,9 @@ export const PipelineForm: React.FunctionComponent = ({ {/* ES request flyout */} {isRequestVisible ? ( + processorStateRef.current?.getData() || { processors: [], on_failure: [] } + } closeFlyout={() => setIsRequestVisible((prevIsRequestVisible) => !prevIsRequestVisible)} /> ) : null} @@ -154,6 +189,9 @@ export const PipelineForm: React.FunctionComponent = ({ {/* Test pipeline flyout */} {isTestingPipeline ? ( + processorStateRef.current?.getData() || { processors: [], on_failure: [] } + } closeFlyout={() => { setIsTestingPipeline((prevIsTestingPipeline) => !prevIsTestingPipeline); }} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx index 9fb5ab55a34ce..52d1a77c1df6d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx @@ -6,22 +6,22 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiSpacer, EuiSwitch, EuiLink } from '@elastic/eui'; +import { EuiSpacer, EuiSwitch } from '@elastic/eui'; -import { - getUseField, - getFormRow, - Field, - JsonEditorField, - useKibana, -} from '../../../shared_imports'; +import { Processor } from '../../../../common/types'; +import { FormDataProvider } from '../../../shared_imports'; +import { PipelineProcessorsEditor, OnUpdateHandler } from '../pipeline_processors_editor'; + +import { getUseField, getFormRow, Field, useKibana } from '../../../shared_imports'; interface Props { + initialProcessors: Processor[]; + initialOnFailureProcessors?: Processor[]; + onProcessorsUpdate: OnUpdateHandler; hasVersion: boolean; - hasOnFailure: boolean; isTestButtonDisabled: boolean; onTestPipelineClick: () => void; + onEditorFlyoutOpen: () => void; isEditing?: boolean; } @@ -29,16 +29,18 @@ const UseField = getUseField({ component: Field }); const FormRow = getFormRow({ titleTag: 'h3' }); export const PipelineFormFields: React.FunctionComponent = ({ + initialProcessors, + initialOnFailureProcessors, + onProcessorsUpdate, isEditing, hasVersion, - hasOnFailure, isTestButtonDisabled, onTestPipelineClick, + onEditorFlyoutOpen, }) => { const { services } = useKibana(); const [isVersionVisible, setIsVersionVisible] = useState(hasVersion); - const [isOnFailureEditorVisible, setIsOnFailureEditorVisible] = useState(hasOnFailure); return ( <> @@ -110,127 +112,31 @@ export const PipelineFormFields: React.FunctionComponent = ({ /> - {/* Processors field */} - - } - description={ - <> - - {i18n.translate('xpack.ingestPipelines.form.processorsDocumentionLink', { - defaultMessage: 'Learn more', - })} - - ), - }} - /> - - + {/* Pipeline Processors Editor */} + + {({ processors, on_failure: onFailure }) => { + const processorProp = + typeof processors === 'string' && processors + ? JSON.parse(processors) + : initialProcessors ?? []; - - - - - } - > - - + const onFailureProp = + typeof onFailure === 'string' && onFailure + ? JSON.parse(onFailure) + : initialOnFailureProcessors ?? []; - {/* On-failure field */} - - } - description={ - <> - - {i18n.translate('xpack.ingestPipelines.form.onFailureDocumentionLink', { - defaultMessage: 'Learn more', - })} - - ), - }} - /> - - - } - checked={isOnFailureEditorVisible} - onChange={(e) => setIsOnFailureEditorVisible(e.target.checked)} - data-test-subj="onFailureToggle" + return ( + - - } - > - {isOnFailureEditorVisible ? ( - - ) : ( - // requires children or a field - // For now, we return an empty
if the editor is not visible -
- )} - + ); + }} + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx index 6dcedca6085af..dd2439433fc41 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_request_flyout/pipeline_request_flyout_provider.tsx @@ -4,13 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, FunctionComponent } from 'react'; import { Pipeline } from '../../../../../common/types'; import { useFormContext } from '../../../../shared_imports'; + +import { ReadProcessorsFunction } from '../types'; + import { PipelineRequestFlyout } from './pipeline_request_flyout'; -export const PipelineRequestFlyoutProvider = ({ closeFlyout }: { closeFlyout: () => void }) => { +interface Props { + closeFlyout: () => void; + readProcessors: ReadProcessorsFunction; +} + +export const PipelineRequestFlyoutProvider: FunctionComponent = ({ + closeFlyout, + readProcessors, +}) => { const form = useFormContext(); const [formData, setFormData] = useState({} as Pipeline); @@ -25,5 +36,10 @@ export const PipelineRequestFlyoutProvider = ({ closeFlyout }: { closeFlyout: () return subscription.unsubscribe; }, [form]); - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_test_flyout/pipeline_test_flyout_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_test_flyout/pipeline_test_flyout_provider.tsx index 351478394595a..7f91672d64df4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_test_flyout/pipeline_test_flyout_provider.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_test_flyout/pipeline_test_flyout_provider.tsx @@ -8,11 +8,19 @@ import React, { useState, useEffect } from 'react'; import { Pipeline } from '../../../../../common/types'; import { useFormContext } from '../../../../shared_imports'; + +import { ReadProcessorsFunction } from '../types'; + import { PipelineTestFlyout, PipelineTestFlyoutProps } from './pipeline_test_flyout'; -type Props = Omit; +interface Props extends Omit { + readProcessors: ReadProcessorsFunction; +} -export const PipelineTestFlyoutProvider: React.FunctionComponent = ({ closeFlyout }) => { +export const PipelineTestFlyoutProvider: React.FunctionComponent = ({ + closeFlyout, + readProcessors, +}) => { const form = useFormContext(); const [formData, setFormData] = useState({} as Pipeline); const [isFormDataValid, setIsFormDataValid] = useState(false); @@ -31,7 +39,7 @@ export const PipelineTestFlyoutProvider: React.FunctionComponent = ({ clo return ( diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/types.ts new file mode 100644 index 0000000000000..bd74f09546ff4 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Pipeline } from '../../../../common/types'; + +export type ReadProcessorsFunction = () => Pick; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.ts new file mode 100644 index 0000000000000..acd61a9bbd01e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.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 { registerTestBed, TestBed } from '../../../../../../../test_utils'; +import { PipelineProcessorsEditor, Props } from '../pipeline_processors_editor.container'; + +const testBedSetup = registerTestBed(PipelineProcessorsEditor, { + doMountAsync: false, +}); + +export interface SetupResult extends TestBed { + actions: { + toggleOnFailure: () => void; + }; +} + +export const setup = async (props: Props): Promise => { + const testBed = await testBedSetup(props); + const toggleOnFailure = () => { + const { find } = testBed; + find('pipelineEditorOnFailureToggle').simulate('click'); + }; + + return { + ...testBed, + actions: { toggleOnFailure }, + }; +}; + +type TestSubject = + | 'pipelineEditorDoneButton' + | 'pipelineEditorOnFailureToggle' + | 'pipelineEditorOnFailureTree'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx new file mode 100644 index 0000000000000..ad0474a94f56c --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setup } from './pipeline_processors_editor.helpers'; +import { Pipeline } from '../../../../../common/types'; + +const testProcessors: Pick = { + processors: [ + { + script: { + source: 'ctx._type = null', + }, + }, + { + gsub: { + field: '_index', + pattern: '(.monitoring-\\w+-)6(-.+)', + replacement: '$17$2', + }, + }, + ], +}; + +describe('Pipeline Editor', () => { + it('provides the same data out it got in if nothing changes', async () => { + const onUpdate = jest.fn(); + + await setup({ + value: { + ...testProcessors, + }, + onFlyoutOpen: jest.fn(), + onUpdate, + isTestButtonDisabled: false, + onTestPipelineClick: jest.fn(), + esDocsBasePath: 'test', + }); + + const { + calls: [[arg]], + } = onUpdate.mock; + + expect(arg.getData()).toEqual(testProcessors); + }); + + it('toggles the on-failure processors', async () => { + const { actions, exists } = await setup({ + value: { + ...testProcessors, + }, + onFlyoutOpen: jest.fn(), + onUpdate: jest.fn(), + isTestButtonDisabled: false, + onTestPipelineClick: jest.fn(), + esDocsBasePath: 'test', + }); + + expect(exists('pipelineEditorOnFailureTree')).toBe(false); + actions.toggleOnFailure(); + expect(exists('pipelineEditorOnFailureTree')).toBe(true); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.tsx new file mode 100644 index 0000000000000..5f9bf87ceca1e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/add_processor_button.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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { usePipelineProcessorsContext } from '../context'; + +export interface Props { + onClick: () => void; +} + +export const AddProcessorButton: FunctionComponent = ({ onClick }) => { + const { + state: { editor }, + } = usePipelineProcessorsContext(); + return ( + + {i18n.translate('xpack.ingestPipelines.pipelineEditor.addProcessorButtonLabel', { + defaultMessage: 'Add a processor', + })} + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts new file mode 100644 index 0000000000000..2d512a6bfa2ed --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/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 { + ProcessorSettingsForm, + ProcessorSettingsFromOnSubmitArg, + OnSubmitHandler, +} from './processor_settings_form'; + +export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree'; + +export { PipelineProcessorsEditorItem } from './pipeline_processors_editor_item/pipeline_processors_editor_item'; + +export { ProcessorRemoveModal } from './processor_remove_modal'; + +export { ProcessorsTitleAndTestButton } from './processors_title_and_test_button'; + +export { OnFailureProcessorsTitle } from './on_failure_processors_title'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx new file mode 100644 index 0000000000000..6451096c897d7 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/on_failure_processors_title.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/eui'; +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { usePipelineProcessorsContext } from '../context'; + +export const OnFailureProcessorsTitle: FunctionComponent = () => { + const { links } = usePipelineProcessorsContext(); + return ( + + + +

+ +

+
+ + + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.onFailureProcessorsDocumentationLink', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> + +
+
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/context_menu.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/context_menu.tsx new file mode 100644 index 0000000000000..bc7d6fdcff357 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/context_menu.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { EuiContextMenuItem, EuiContextMenuPanel, EuiPopover, EuiButtonIcon } from '@elastic/eui'; + +import { editorItemMessages } from './messages'; + +interface Props { + disabled: boolean; + showAddOnFailure: boolean; + onDuplicate: () => void; + onDelete: () => void; + onAddOnFailure: () => void; +} + +export const ContextMenu: FunctionComponent = ({ + showAddOnFailure, + onDuplicate, + onAddOnFailure, + onDelete, + disabled, +}) => { + const [isOpen, setIsOpen] = useState(false); + + const contextMenuItems = [ + { + setIsOpen(false); + onDuplicate(); + }} + > + {editorItemMessages.duplicateButtonLabel} + , + showAddOnFailure ? ( + { + setIsOpen(false); + onAddOnFailure(); + }} + > + {editorItemMessages.addOnFailureButtonLabel} + + ) : undefined, + { + setIsOpen(false); + onDelete(); + }} + > + {editorItemMessages.deleteButtonLabel} + , + ].filter(Boolean) as JSX.Element[]; + + return ( + setIsOpen(false)} + button={ + setIsOpen((v) => !v)} + iconType="boxesHorizontal" + aria-label={editorItemMessages.moreButtonAriaLabel} + /> + } + > + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/index.ts new file mode 100644 index 0000000000000..02bafdb326024 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/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 { PipelineProcessorsEditorItem, Handlers } from './pipeline_processors_editor_item'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx new file mode 100644 index 0000000000000..e0b67bc907ca9 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useState, useEffect, useCallback } from 'react'; +import { EuiFieldText, EuiText, keyCodes } from '@elastic/eui'; + +export interface Props { + placeholder: string; + ariaLabel: string; + onChange: (value: string) => void; + text?: string; +} + +export const InlineTextInput: FunctionComponent = ({ + placeholder, + text, + ariaLabel, + onChange, +}) => { + const [isShowingTextInput, setIsShowingTextInput] = useState(false); + const [textValue, setTextValue] = useState(text ?? ''); + + const content = isShowingTextInput ? ( + el?.focus()} + onChange={(event) => setTextValue(event.target.value)} + /> + ) : ( + + {text || {placeholder}} + + ); + + const submitChange = useCallback(() => { + setIsShowingTextInput(false); + onChange(textValue); + }, [setIsShowingTextInput, onChange, textValue]); + + useEffect(() => { + const keyboardListener = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE || event.code === 'Escape') { + setIsShowingTextInput(false); + } + if (event.keyCode === keyCodes.ENTER || event.code === 'Enter') { + submitChange(); + } + }; + if (isShowingTextInput) { + window.addEventListener('keyup', keyboardListener); + } + return () => { + window.removeEventListener('keyup', keyboardListener); + }; + }, [isShowingTextInput, submitChange, setIsShowingTextInput]); + + return ( +
setIsShowingTextInput(true)} + onBlur={submitChange} + > + {content} +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/messages.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/messages.ts new file mode 100644 index 0000000000000..67dbf2708d665 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/messages.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 { i18n } from '@kbn/i18n'; + +export const editorItemMessages = { + moveButtonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.item.moveButtonLabel', { + defaultMessage: 'Move this processor', + }), + editorButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.editButtonAriaLabel', + { + defaultMessage: 'Edit this processor', + } + ), + duplicateButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.moreMenu.duplicateButtonLabel', + { + defaultMessage: 'Duplicate this processor', + } + ), + addOnFailureButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.moreMenu.addOnFailureHandlerButtonLabel', + { + defaultMessage: 'Add on failure handler', + } + ), + cancelMoveButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.cancelMoveButtonAriaLabel', + { + defaultMessage: 'Cancel moving this processor', + } + ), + deleteButtonLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.moreMenu.deleteButtonLabel', + { + defaultMessage: 'Delete', + } + ), + moreButtonAriaLabel: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.moreButtonAriaLabel', + { + defaultMessage: 'Show more actions for this processor', + } + ), + processorTypeLabel: ({ type }: { type: string }) => + i18n.translate('xpack.ingestPipelines.pipelineEditor.item.textInputAriaLabel', { + defaultMessage: 'Provide a description for this {type} processor', + values: { type }, + }), + descriptionPlaceholder: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.item.descriptionPlaceholder', + { defaultMessage: 'No description' } + ), +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss new file mode 100644 index 0000000000000..a17e644853847 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss @@ -0,0 +1,17 @@ +.pipelineProcessorsEditor__item { + &__textContainer { + padding: 4px; + border-radius: 2px; + + transition: border-color .3s; + border: 2px solid #FFF; + + &:hover { + border: 2px solid $euiColorLightShade; + } + } + &__textInput { + height: 21px; + min-width: 100px; + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx new file mode 100644 index 0000000000000..0e47b3ef7cf88 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, memo } from 'react'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; + +import { ProcessorInternal, ProcessorSelector } from '../../types'; + +import { usePipelineProcessorsContext } from '../../context'; + +import './pipeline_processors_editor_item.scss'; + +import { InlineTextInput } from './inline_text_input'; +import { ContextMenu } from './context_menu'; +import { editorItemMessages } from './messages'; + +export interface Handlers { + onMove: () => void; + onCancelMove: () => void; +} + +export interface Props { + processor: ProcessorInternal; + selected: boolean; + handlers: Handlers; + selector: ProcessorSelector; + description?: string; +} + +export const PipelineProcessorsEditorItem: FunctionComponent = memo( + ({ processor, description, handlers: { onCancelMove, onMove }, selector, selected }) => { + const { + state: { editor, processorsDispatch }, + } = usePipelineProcessorsContext(); + + const disabled = editor.mode.id !== 'idle'; + const isDarkBold = + editor.mode.id !== 'editingProcessor' || processor.id === editor.mode.arg.processor.id; + + return ( + + + + + + {processor.type} + + + + { + let nextOptions: Record; + if (!nextDescription) { + const { description: __, ...restOptions } = processor.options; + nextOptions = restOptions; + } else { + nextOptions = { + ...processor.options, + description: nextDescription, + }; + } + processorsDispatch({ + type: 'updateProcessor', + payload: { + processor: { + ...processor, + options: nextOptions, + }, + selector, + }, + }); + }} + ariaLabel={editorItemMessages.processorTypeLabel({ type: processor.type })} + text={description} + placeholder={editorItemMessages.descriptionPlaceholder} + /> + + + { + editor.setMode({ + id: 'editingProcessor', + arg: { processor, selector }, + }); + }} + /> + + + {selected ? ( + + ) : ( + + + + )} + + + + + { + editor.setMode({ id: 'creatingProcessor', arg: { selector } }); + }} + onDelete={() => { + editor.setMode({ id: 'removingProcessor', arg: { selector } }); + }} + onDuplicate={() => { + processorsDispatch({ + type: 'duplicateProcessor', + payload: { + source: selector, + }, + }); + }} + /> + + + ); + } +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_remove_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_remove_modal.tsx new file mode 100644 index 0000000000000..c38e470b36699 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_remove_modal.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { ProcessorInternal, ProcessorSelector } from '../types'; + +interface Props { + processor: ProcessorInternal; + selector: ProcessorSelector; + onResult: (arg: { confirmed: boolean; selector: ProcessorSelector }) => void; +} + +export const ProcessorRemoveModal = ({ processor, onResult, selector }: Props) => { + return ( + + + } + onCancel={() => onResult({ confirmed: false, selector })} + onConfirm={() => onResult({ confirmed: true, selector })} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/documentation_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/documentation_button.tsx new file mode 100644 index 0000000000000..b1fd9e97aa23d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/documentation_button.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + processorLabel: string; + docLink: string; +} + +export const DocumentationButton: FunctionComponent = ({ processorLabel, docLink }) => { + return ( + + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.settingsForm.learnMoreLabelLink.processor', + { defaultMessage: '{processorLabel} documentation', values: { processorLabel } } + )} + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/index.ts new file mode 100644 index 0000000000000..1a7da4891967a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + ProcessorSettingsForm, + ProcessorSettingsFromOnSubmitArg, + OnSubmitHandler, +} from './processor_settings_form.container'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/map_processor_type_to_form.tsx new file mode 100644 index 0000000000000..5993d7fb3f87a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/map_processor_type_to_form.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FunctionComponent } from 'react'; + +// import { SetProcessor } from './processors/set'; +// import { Gsub } from './processors/gsub'; + +interface FieldsFormDescriptor { + FieldsComponent?: FunctionComponent; + docLinkPath: string; + /** + * A sentence case label that can be displayed to users + */ + label: string; +} + +const mapProcessorTypeToFormDescriptor: Record = { + append: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/append-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.append', { + defaultMessage: 'Append', + }), + }, + bytes: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/bytes-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.bytes', { + defaultMessage: 'Bytes', + }), + }, + circle: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/ingest-circle-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.circle', { + defaultMessage: 'Circle', + }), + }, + convert: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/convert-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.convert', { + defaultMessage: 'Convert', + }), + }, + csv: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/csv-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.csv', { + defaultMessage: 'CSV', + }), + }, + date: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/date-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.date', { + defaultMessage: 'Date', + }), + }, + date_index_name: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/date-index-name-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.dateIndexName', { + defaultMessage: 'Date Index Name', + }), + }, + dissect: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/dissect-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.dissect', { + defaultMessage: 'Dissect', + }), + }, + dot_expander: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/dot-expand-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.dotExpander', { + defaultMessage: 'Dot Expander', + }), + }, + drop: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/drop-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.drop', { + defaultMessage: 'Drop', + }), + }, + enrich: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/enrich-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.enrich', { + defaultMessage: 'Enrich', + }), + }, + fail: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/fail-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.fail', { + defaultMessage: 'Fail', + }), + }, + foreach: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/foreach-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.foreach', { + defaultMessage: 'Foreach', + }), + }, + geoip: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/geoip-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.geoip', { + defaultMessage: 'GeoIP', + }), + }, + gsub: { + FieldsComponent: undefined, + docLinkPath: '/gsub-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.gsub', { + defaultMessage: 'Gsub', + }), + }, + html_strip: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/htmlstrip-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.htmlStrip', { + defaultMessage: 'HTML Strip', + }), + }, + inference: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/inference-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.inference', { + defaultMessage: 'Inference', + }), + }, + join: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/join-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.join', { + defaultMessage: 'Join', + }), + }, + json: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/json-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.json', { + defaultMessage: 'JSON', + }), + }, + kv: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/kv-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.kv', { + defaultMessage: 'KV', + }), + }, + lowercase: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/lowercase-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.lowercase', { + defaultMessage: 'Lowercase', + }), + }, + pipeline: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/pipeline-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.pipeline', { + defaultMessage: 'Pipeline', + }), + }, + remove: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/remove-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.remove', { + defaultMessage: 'Remove', + }), + }, + rename: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/rename-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.rename', { + defaultMessage: 'Rename', + }), + }, + script: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/script-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.script', { + defaultMessage: 'Script', + }), + }, + set_security_user: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/ingest-node-set-security-user-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.setSecurityUser', { + defaultMessage: 'Set Security User', + }), + }, + split: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/split-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.split', { + defaultMessage: 'Split', + }), + }, + sort: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/sort-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.sort', { + defaultMessage: 'Sort', + }), + }, + trim: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/trim-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.trim', { + defaultMessage: 'Trim', + }), + }, + uppercase: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/uppercase-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.uppercase', { + defaultMessage: 'Uppercase', + }), + }, + urldecode: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/urldecode-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.urldecode', { + defaultMessage: 'URL Decode', + }), + }, + user_agent: { + FieldsComponent: undefined, // TODO: Implement + docLinkPath: '/user-agent-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.userAgent', { + defaultMessage: 'User Agent', + }), + }, + + // --- The below processor descriptors have components implemented --- + set: { + FieldsComponent: undefined, + docLinkPath: '/set-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.set', { + defaultMessage: 'Set', + }), + }, + grok: { + FieldsComponent: undefined, + docLinkPath: '/grok-processor.html', + label: i18n.translate('xpack.ingestPipelines.processors.label.grok', { + defaultMessage: 'Grok', + }), + }, +}; + +export const types = Object.keys(mapProcessorTypeToFormDescriptor).sort(); + +export type ProcessorType = keyof typeof mapProcessorTypeToFormDescriptor; + +export const getProcessorFormDescriptor = ( + type: ProcessorType | string +): FieldsFormDescriptor | undefined => { + return mapProcessorTypeToFormDescriptor[type as ProcessorType]; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.container.tsx new file mode 100644 index 0000000000000..d76e9225c1a13 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.container.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useCallback, useEffect } from 'react'; + +import { useForm, OnFormUpdateArg, FormData } from '../../../../../shared_imports'; +import { ProcessorInternal } from '../../types'; + +import { ProcessorSettingsForm as ViewComponent } from './processor_settings_form'; + +export type ProcessorSettingsFromOnSubmitArg = Omit; + +export type OnSubmitHandler = (processor: ProcessorSettingsFromOnSubmitArg) => void; + +export type OnFormUpdateHandler = (form: OnFormUpdateArg) => void; + +interface Props { + onFormUpdate: OnFormUpdateHandler; + onSubmit: OnSubmitHandler; + isOnFailure: boolean; + onOpen: () => void; + onClose: () => void; + processor?: ProcessorInternal; +} + +export const ProcessorSettingsForm: FunctionComponent = ({ + processor, + onFormUpdate, + onSubmit, + ...rest +}) => { + const handleSubmit = useCallback( + async (data: FormData, isValid: boolean) => { + if (isValid) { + const { type, customOptions, ...options } = data; + onSubmit({ + type, + options: customOptions ? customOptions : options, + }); + } + }, + [onSubmit] + ); + + const { form } = useForm({ + defaultValue: processor?.options, + onSubmit: handleSubmit, + }); + + useEffect(() => { + const subscription = form.subscribe(onFormUpdate); + return subscription.unsubscribe; + + // TODO: Address this issue + // For some reason adding `form` object to the dependencies array here is causing an + // infinite update loop. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onFormUpdate]); + + return ; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx new file mode 100644 index 0000000000000..81b5731a96d5f --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processor_settings_form.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FunctionComponent, memo, useEffect } from 'react'; +import { + EuiButton, + EuiHorizontalRule, + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { Form, useForm, FormDataProvider } from '../../../../../shared_imports'; +import { usePipelineProcessorsContext } from '../../context'; +import { ProcessorInternal } from '../../types'; + +import { DocumentationButton } from './documentation_button'; +import { ProcessorSettingsFromOnSubmitArg } from './processor_settings_form.container'; +import { getProcessorFormDescriptor } from './map_processor_type_to_form'; +import { CommonProcessorFields, ProcessorTypeField } from './processors/common_fields'; +import { Custom } from './processors/custom'; + +export type OnSubmitHandler = (processor: ProcessorSettingsFromOnSubmitArg) => void; + +export interface Props { + isOnFailure: boolean; + processor?: ProcessorInternal; + form: ReturnType['form']; + onClose: () => void; + onOpen: () => void; +} + +export const ProcessorSettingsForm: FunctionComponent = memo( + ({ processor, form, isOnFailure, onClose, onOpen }) => { + const { + links: { esDocsBasePath }, + } = usePipelineProcessorsContext(); + + const flyoutTitleContent = isOnFailure ? ( + + ) : ( + + ); + + useEffect( + () => { + onOpen(); + }, + [] /* eslint-disable-line react-hooks/exhaustive-deps */ + ); + + return ( + + + + + +
+ +

{flyoutTitleContent}

+
+
+
+ + + + {({ type }) => { + const formDescriptor = getProcessorFormDescriptor(type as any); + + if (formDescriptor) { + return ( + + ); + } + return null; + }} + + +
+
+ + + + + + + {(arg: any) => { + const { type } = arg; + let formContent: React.ReactNode | undefined; + + if (type?.length) { + const formDescriptor = getProcessorFormDescriptor(type as any); + + if (formDescriptor?.FieldsComponent) { + formContent = ( + <> + + + + ); + } else { + formContent = ; + } + + return ( + <> + {formContent} + + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.settingsForm.submitButtonLabel', + { defaultMessage: 'Submit' } + )} + + + ); + } + + // If the user has not yet defined a type, we do not show any settings fields + return null; + }} + + +
+ + ); + }, + (previous, current) => { + return previous.processor === current.processor; + } +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/common_processor_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/common_processor_fields.tsx new file mode 100644 index 0000000000000..4802653f9e680 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/common_processor_fields.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + FieldConfig, + UseField, + FIELD_TYPES, + Field, + ToggleField, +} from '../../../../../../../shared_imports'; + +const ignoreFailureConfig: FieldConfig = { + defaultValue: false, + label: i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.commonFields.ignoreFailureFieldLabel', + { + defaultMessage: 'Ignore failure', + } + ), + type: FIELD_TYPES.TOGGLE, +}; + +const ifConfig: FieldConfig = { + defaultValue: undefined, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.commonFields.ifFieldLabel', { + defaultMessage: 'Condition (optional)', + }), + type: FIELD_TYPES.TEXT, +}; + +const tagConfig: FieldConfig = { + defaultValue: undefined, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.commonFields.tagFieldLabel', { + defaultMessage: 'Tag (optional)', + }), + type: FIELD_TYPES.TEXT, +}; + +export const CommonProcessorFields: FunctionComponent = () => { + return ( + <> + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/index.ts new file mode 100644 index 0000000000000..f3fa0e028faaa --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/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 { ProcessorTypeField } from './processor_type_field'; + +export { CommonProcessorFields } from './common_processor_fields'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/processor_type_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/processor_type_field.tsx new file mode 100644 index 0000000000000..6c86fc16bcdd0 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/common_fields/processor_type_field.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { FunctionComponent } from 'react'; +import { + FIELD_TYPES, + FieldConfig, + UseField, + fieldValidators, + ComboBoxField, +} from '../../../../../../../shared_imports'; +import { types } from '../../map_processor_type_to_form'; + +interface Props { + initialType?: string; +} + +const { emptyField } = fieldValidators; + +const typeConfig: FieldConfig = { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.typeField.typeFieldLabel', { + defaultMessage: 'Processor', + }), + deserializer: (value: string | undefined) => { + if (value) { + return [value]; + } + return []; + }, + serializer: (value: string[]) => { + return value[0]; + }, + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.typeField.fieldRequiredError', { + defaultMessage: 'A type is required.', + }) + ), + }, + ], +}; + +export const ProcessorTypeField: FunctionComponent = ({ initialType }) => { + return ( + ({ label: type, value: type })), + noSuggestions: false, + singleSelection: { + asPlainText: true, + }, + }, + }} + /> + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/custom.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/custom.tsx new file mode 100644 index 0000000000000..61fc31a7b472a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/custom.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + FieldConfig, + FIELD_TYPES, + fieldValidators, + UseField, + JsonEditorField, +} from '../../../../../../shared_imports'; + +const { emptyField, isJsonField } = fieldValidators; + +const customConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.customForm.optionsFieldLabel', { + defaultMessage: 'Configuration options', + }), + serializer: (value: string) => { + try { + return JSON.parse(value); + } catch (error) { + // swallow error and return non-parsed value; + return value; + } + }, + deserializer: (value: any) => { + if (value === '') { + return '{\n\n}'; + } + return JSON.stringify(value, null, 2); + }, + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.customForm.configurationRequiredError', + { + defaultMessage: 'Configuration options are required.', + } + ) + ), + }, + { + validator: isJsonField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.customForm.invalidJsonError', { + defaultMessage: 'The input is not valid.', + }) + ), + }, + ], +}; + +interface Props { + defaultOptions?: any; +} + +/** + * This is a catch-all component to support settings for custom processors + * or existing processors not yet supported by the UI. + * + * We store the settings in a field called "customOptions" + **/ +export const Custom: FunctionComponent = ({ defaultOptions }) => { + return ( + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/gsub.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/gsub.tsx new file mode 100644 index 0000000000000..77f85e61eff6b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/gsub.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + FieldConfig, + FIELD_TYPES, + fieldValidators, + ToggleField, + UseField, + Field, +} from '../../../../../../shared_imports'; + +const { emptyField } = fieldValidators; + +const fieldConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.fieldFieldLabel', { + defaultMessage: 'Field', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.fieldRequiredError', { + defaultMessage: 'A field value is required.', + }) + ), + }, + ], +}; + +const patternConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.patternFieldLabel', { + defaultMessage: 'Pattern', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.patternRequiredError', { + defaultMessage: 'A pattern value is required.', + }) + ), + }, + ], +}; + +const replacementConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.replacementFieldLabel', { + defaultMessage: 'Replacement', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.replacementRequiredError', { + defaultMessage: 'A replacement value is required.', + }) + ), + }, + ], +}; + +const targetConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.targetFieldLabel', { + defaultMessage: 'Target field (optional)', + }), +}; + +const ignoreMissingConfig: FieldConfig = { + defaultValue: false, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.gsubForm.ignoreMissingFieldLabel', { + defaultMessage: 'Ignore missing', + }), + type: FIELD_TYPES.TOGGLE, +}; + +export const Gsub: FunctionComponent = () => { + return ( + <> + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/set.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/set.tsx new file mode 100644 index 0000000000000..1ba6a14d0448d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_settings_form/processors/set.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + FieldConfig, + FIELD_TYPES, + fieldValidators, + ToggleField, + UseField, + Field, +} from '../../../../../../shared_imports'; + +const { emptyField } = fieldValidators; + +const fieldConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.fieldFieldLabel', { + defaultMessage: 'Field', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.fieldRequiredError', { + defaultMessage: 'A field value is required.', + }) + ), + }, + ], +}; + +const valueConfig: FieldConfig = { + type: FIELD_TYPES.TEXT, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.valueFieldLabel', { + defaultMessage: 'Value', + }), + validations: [ + { + validator: emptyField( + i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.valueRequiredError', { + defaultMessage: 'A value to set is required.', + }) + ), + }, + ], +}; + +const overrideConfig: FieldConfig = { + defaultValue: false, + label: i18n.translate('xpack.ingestPipelines.pipelineEditor.setForm.overrideFieldLabel', { + defaultMessage: 'Override', + }), + type: FIELD_TYPES.TOGGLE, +}; + +/** + * Disambiguate name from the Set data structure + */ +export const SetProcessor: FunctionComponent = () => { + return ( + <> + + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_title_and_test_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_title_and_test_button.tsx new file mode 100644 index 0000000000000..6d1e2610b5c2b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_title_and_test_button.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { usePipelineProcessorsContext } from '../context'; + +export interface Props { + onTestPipelineClick: () => void; + isTestButtonDisabled: boolean; +} + +export const ProcessorsTitleAndTestButton: FunctionComponent = ({ + onTestPipelineClick, + isTestButtonDisabled, +}) => { + const { links } = usePipelineProcessorsContext(); + return ( + + + +

+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.processorsTreeTitle', { + defaultMessage: 'Processors', + })} +

+
+ + + {i18n.translate( + 'xpack.ingestPipelines.pipelineEditor.processorsDocumentationLink', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> + +
+ + + + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/drop_zone_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/drop_zone_button.tsx new file mode 100644 index 0000000000000..a47886292cf32 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/drop_zone_button.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { FunctionComponent } from 'react'; +import classNames from 'classnames'; +import { EuiButtonIcon, EuiFlexItem } from '@elastic/eui'; + +export interface Props { + isDisabled: boolean; + onClick: (event: React.MouseEvent) => void; +} + +const MOVE_HERE_LABEL = i18n.translate('xpack.ingestPipelines.pipelineEditor.moveTargetLabel', { + defaultMessage: 'Move here', +}); + +export const DropZoneButton: FunctionComponent = ({ onClick, isDisabled }) => { + const containerClasses = classNames({ + 'pipelineProcessorsEditor__tree__dropZoneContainer--active': !isDisabled, + }); + const buttonClasses = classNames({ + 'pipelineProcessorsEditor__tree__dropZoneButton--active': !isDisabled, + }); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/index.ts new file mode 100644 index 0000000000000..e9548624d2cef --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DropZoneButton } from './drop_zone_button'; + +export { PrivateTree } from './private_tree'; + +export { TreeNode } from './tree_node'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/private_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/private_tree.tsx new file mode 100644 index 0000000000000..bdc6b2eb44e2d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/private_tree.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, MutableRefObject, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { AutoSizer, List, WindowScroller } from 'react-virtualized'; + +import { DropSpecialLocations } from '../../../constants'; +import { ProcessorInternal, ProcessorSelector } from '../../../types'; +import { isChildPath } from '../../../processors_reducer'; + +import { DropZoneButton } from '.'; +import { TreeNode } from '.'; +import { calculateItemHeight } from '../utils'; +import { OnActionHandler, ProcessorInfo } from '../processors_tree'; + +export interface PrivateProps { + processors: ProcessorInternal[]; + selector: ProcessorSelector; + onAction: OnActionHandler; + level: number; + movingProcessor?: ProcessorInfo; + // Only passed into the top level list + windowScrollerRef?: MutableRefObject; + listRef?: MutableRefObject; +} + +const isDropZoneAboveDisabled = (processor: ProcessorInfo, selectedProcessor: ProcessorInfo) => { + return Boolean( + // Is the selected node first in a list? + (!selectedProcessor.aboveId && selectedProcessor.id === processor.id) || + isChildPath(selectedProcessor.selector, processor.selector) + ); +}; + +const isDropZoneBelowDisabled = (processor: ProcessorInfo, selectedProcessor: ProcessorInfo) => { + return ( + processor.id === selectedProcessor.id || + processor.belowId === selectedProcessor.id || + isChildPath(selectedProcessor.selector, processor.selector) + ); +}; + +/** + * Recursively rendering tree component for ingest pipeline processors. + * + * Note: this tree should start at level 1. It is the only level at + * which we render the optimised virtual component. This gives a + * massive performance boost to this component which can get very tall. + * + * The first level list also contains the outside click listener which + * enables users to click outside of the tree and cancel moving a + * processor. + */ +export const PrivateTree: FunctionComponent = ({ + processors, + selector, + movingProcessor, + onAction, + level, + windowScrollerRef, + listRef, +}) => { + const renderRow = ({ + idx, + info, + processor, + }: { + idx: number; + info: ProcessorInfo; + processor: ProcessorInternal; + }) => { + return ( + <> + {idx === 0 ? ( + { + event.preventDefault(); + onAction({ + type: 'move', + payload: { + destination: selector.concat(DropSpecialLocations.top), + source: movingProcessor!.selector, + }, + }); + }} + isDisabled={Boolean( + !movingProcessor || isDropZoneAboveDisabled(info, movingProcessor!) + )} + /> + ) : undefined} + + + + { + event.preventDefault(); + onAction({ + type: 'move', + payload: { + destination: selector.concat(String(idx + 1)), + source: movingProcessor!.selector, + }, + }); + }} + /> + + ); + }; + + useEffect(() => { + if (windowScrollerRef && windowScrollerRef.current) { + windowScrollerRef.current.updatePosition(); + } + if (listRef && listRef.current) { + listRef.current.recomputeRowHeights(); + } + }, [processors, listRef, windowScrollerRef, movingProcessor]); + + // A list optimized to handle very many items. + const renderVirtualList = () => { + return ( + + {({ height, registerChild, isScrolling, onChildScroll, scrollTop }: any) => { + return ( + + + {({ width }) => { + return ( +
+ { + const processor = processors[index]; + return calculateItemHeight({ + processor, + isFirstInArray: index === 0, + }); + }} + rowRenderer={({ index: idx, style }) => { + const processor = processors[idx]; + const above = processors[idx - 1]; + const below = processors[idx + 1]; + const info: ProcessorInfo = { + id: processor.id, + selector: selector.concat(String(idx)), + aboveId: above?.id, + belowId: below?.id, + }; + + return ( +
+ {renderRow({ processor, info, idx })} +
+ ); + }} + processors={processors} + /> +
+ ); + }} +
+
+ ); + }} +
+ ); + }; + + if (level === 1) { + // Only render the optimised list for the top level list because that is the list + // that will almost certainly be the tallest + return renderVirtualList(); + } + + return ( + + {processors.map((processor, idx) => { + const above = processors[idx - 1]; + const below = processors[idx + 1]; + const info: ProcessorInfo = { + id: processor.id, + selector: selector.concat(String(idx)), + aboveId: above?.id, + belowId: below?.id, + }; + + return
{renderRow({ processor, idx, info })}
; + })} +
+ ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx new file mode 100644 index 0000000000000..ebe4ca4962b4c --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/tree_node.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useMemo } from 'react'; +import classNames from 'classnames'; +import { i18n } from '@kbn/i18n'; +import { EuiPanel, EuiText } from '@elastic/eui'; + +import { ProcessorInternal } from '../../../types'; + +import { ProcessorInfo, OnActionHandler } from '../processors_tree'; + +import { PipelineProcessorsEditorItem, Handlers } from '../../pipeline_processors_editor_item'; +import { AddProcessorButton } from '../../add_processor_button'; + +import { PrivateTree } from './private_tree'; + +export interface Props { + processor: ProcessorInternal; + processorInfo: ProcessorInfo; + onAction: OnActionHandler; + level: number; + movingProcessor?: ProcessorInfo; +} + +const INDENTATION_PX = 34; + +export const TreeNode: FunctionComponent = ({ + processor, + processorInfo, + onAction, + movingProcessor, + level, +}) => { + const stringSelector = processorInfo.selector.join('.'); + const handlers = useMemo((): Handlers => { + return { + onMove: () => { + onAction({ type: 'selectToMove', payload: { info: processorInfo } }); + }, + onCancelMove: () => { + onAction({ type: 'cancelMove' }); + }, + }; + }, [onAction, stringSelector, processor]); // eslint-disable-line react-hooks/exhaustive-deps + + const selected = movingProcessor?.id === processor.id; + + const panelClasses = classNames({ + 'pipelineProcessorsEditor__tree__item--selected': selected, + }); + + const renderOnFailureHandlersTree = () => { + if (!processor.onFailure?.length) { + return; + } + + const onFailureHandlerLabelClasses = classNames({ + 'pipelineProcessorsEditor__tree__onFailureHandlerLabel--withDropZone': + movingProcessor != null && + movingProcessor.id !== processor.onFailure[0].id && + movingProcessor.id !== processor.id, + }); + + return ( +
+
+ + {i18n.translate('xpack.ingestPipelines.pipelineEditor.onFailureProcessorsLabel', { + defaultMessage: 'Failure handlers', + })} + +
+ + + onAction({ + type: 'addProcessor', + payload: { target: processorInfo.selector.concat('onFailure') }, + }) + } + /> +
+ ); + }; + + return ( + + + {renderOnFailureHandlersTree()} + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/index.ts new file mode 100644 index 0000000000000..5a09794fd4bee --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/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 { ProcessorsTree, OnActionHandler, ProcessorInfo } from './processors_tree'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.scss new file mode 100644 index 0000000000000..ad9058cea5e18 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.scss @@ -0,0 +1,74 @@ +@import '@elastic/eui/src/global_styling/variables/size'; + +.pipelineProcessorsEditor__tree { + + &__container { + background-color: $euiColorLightestShade; + padding: $euiSizeS; + } + + &__dropZoneContainer { + margin: 2px; + visibility: hidden; + border: 2px dashed $euiColorLightShade; + height: 12px; + border-radius: 2px; + + transition: border .5s; + + &--active { + &:hover { + border: 2px dashed $euiColorPrimary; + } + visibility: visible; + } + } + + &__dropZoneButton { + height: 8px; + opacity: 0; + text-decoration: none !important; + + &--active { + &:hover { + transform: none !important; + } + } + + &:disabled { + cursor: default !important; + & > * { + cursor: default !important; + } + } + } + + &__onFailureHandlerLabelContainer { + position: relative; + height: 14px; + } + &__onFailureHandlerLabel { + position: absolute; + bottom: -16px; + &--withDropZone { + bottom: -4px; + } + } + + + &__onFailureHandlerContainer { + margin-top: $euiSizeS; + margin-bottom: $euiSizeS; + & > * { + overflow: visible; + } + } + + &__item { + transition: border-color 1s; + min-height: 50px; + &--selected { + border: 1px solid $euiColorPrimary; + } + } +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx new file mode 100644 index 0000000000000..d0661913515b2 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { FunctionComponent, memo, useRef, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, keyCodes } from '@elastic/eui'; +import { List, WindowScroller } from 'react-virtualized'; + +import { ProcessorInternal, ProcessorSelector } from '../../types'; + +import './processors_tree.scss'; +import { AddProcessorButton } from '../add_processor_button'; +import { PrivateTree } from './components'; + +export interface ProcessorInfo { + id: string; + selector: ProcessorSelector; + aboveId?: string; + belowId?: string; +} + +export type Action = + | { type: 'move'; payload: { source: ProcessorSelector; destination: ProcessorSelector } } + | { type: 'selectToMove'; payload: { info: ProcessorInfo } } + | { type: 'cancelMove' } + | { type: 'addProcessor'; payload: { target: ProcessorSelector } }; + +export type OnActionHandler = (action: Action) => void; + +export interface Props { + processors: ProcessorInternal[]; + baseSelector: ProcessorSelector; + onAction: OnActionHandler; + movingProcessor?: ProcessorInfo; + 'data-test-subj'?: string; +} + +/** + * This component is the public interface to our optimised tree rendering private components and + * also contains top-level state concerns for an instance of the component + */ +export const ProcessorsTree: FunctionComponent = memo((props) => { + const { processors, baseSelector, onAction, movingProcessor } = props; + // These refs are created here so they can be shared with all + // recursively rendered trees. Their values should come from react-virtualized + // List component and WindowScroller component. + const windowScrollerRef = useRef(null); + const listRef = useRef(null); + + useEffect(() => { + const cancelMoveKbListener = (event: KeyboardEvent) => { + // x-browser support per https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode + if (event.keyCode === keyCodes.ESCAPE || event.code === 'Escape') { + onAction({ type: 'cancelMove' }); + } + }; + const cancelMoveClickListener = (ev: any) => { + onAction({ type: 'cancelMove' }); + }; + // Give the browser a chance to flush any click events including the click + // event that triggered any state transition into selecting a processor to move + setTimeout(() => { + if (movingProcessor) { + window.addEventListener('keyup', cancelMoveKbListener); + window.addEventListener('click', cancelMoveClickListener); + } else { + window.removeEventListener('keyup', cancelMoveKbListener); + window.removeEventListener('click', cancelMoveClickListener); + } + }); + return () => { + window.removeEventListener('keyup', cancelMoveKbListener); + window.removeEventListener('click', cancelMoveClickListener); + }; + }, [movingProcessor, onAction]); + + return ( + + + + + + + + { + onAction({ type: 'addProcessor', payload: { target: baseSelector } }); + }} + /> + + + + + ); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/utils.ts new file mode 100644 index 0000000000000..457e335602b9b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/utils.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 { ProcessorInternal } from '../../types'; + +// These values are tied to the style and heights following components: +// Do not change these numbers without testing the component for visual +// regressions! +// - ./components/tree_node.tsx +// - ./components/drop_zone_button.tsx +// - ./components/pipeline_processors_editor_item.tsx +const itemHeightsPx = { + WITHOUT_NESTED_ITEMS: 67, + WITH_NESTED_ITEMS: 137, + TOP_PADDING: 16, +}; + +export const calculateItemHeight = ({ + processor, + isFirstInArray, +}: { + processor: ProcessorInternal; + isFirstInArray: boolean; +}): number => { + const padding = isFirstInArray ? itemHeightsPx.TOP_PADDING : 0; + + if (!processor.onFailure?.length) { + return padding + itemHeightsPx.WITHOUT_NESTED_ITEMS; + } + + return ( + padding + + itemHeightsPx.WITH_NESTED_ITEMS + + processor.onFailure.reduce((acc, p, idx) => { + return acc + calculateItemHeight({ processor: p, isFirstInArray: idx === 0 }); + }, 0) + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/constants.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/constants.ts new file mode 100644 index 0000000000000..46e3d1c803fd5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/constants.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 enum DropSpecialLocations { + top = 'TOP', + bottom = 'BOTTOM', +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context.tsx new file mode 100644 index 0000000000000..fbc06f41208fe --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, Dispatch, FunctionComponent, useContext, useState } from 'react'; +import { EditorMode } from './types'; +import { ProcessorsDispatch } from './processors_reducer'; + +interface Links { + esDocsBasePath: string; +} + +const PipelineProcessorsContext = createContext<{ + links: Links; + state: { + processorsDispatch: ProcessorsDispatch; + editor: { + mode: EditorMode; + setMode: Dispatch; + }; + }; +}>({} as any); + +interface Props { + links: Links; + processorsDispatch: ProcessorsDispatch; +} + +export const PipelineProcessorsContextProvider: FunctionComponent = ({ + links, + children, + processorsDispatch, +}) => { + const [mode, setMode] = useState({ id: 'idle' }); + return ( + + {children} + + ); +}; + +export const usePipelineProcessorsContext = () => { + const ctx = useContext(PipelineProcessorsContext); + if (!ctx) { + throw new Error( + 'usePipelineProcessorsContext can only be used inside of PipelineProcessorsContextProvider' + ); + } + return ctx; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.ts new file mode 100644 index 0000000000000..fa1d041bdaba3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.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 uuid from 'uuid'; +import { Processor } from '../../../../common/types'; +import { ProcessorInternal } from './types'; + +export interface DeserializeArgs { + processors: Processor[]; + onFailure?: Processor[]; +} + +export interface DeserializeResult { + processors: ProcessorInternal[]; + onFailure?: ProcessorInternal[]; +} + +const getProcessorType = (processor: Processor): string => { + /** + * See the definition of {@link ProcessorInternal} for why this works to extract the + * processor type. + */ + return Object.keys(processor)[0]!; +}; + +const convertToPipelineInternalProcessor = (processor: Processor): ProcessorInternal => { + const type = getProcessorType(processor); + const { on_failure: originalOnFailure, ...options } = processor[type]; + const onFailure = originalOnFailure?.length + ? convertProcessors(originalOnFailure) + : (originalOnFailure as ProcessorInternal[] | undefined); + return { + id: uuid.v4(), + type, + onFailure, + options, + }; +}; + +const convertProcessors = (processors: Processor[]) => { + const convertedProcessors = []; + + for (const processor of processors) { + convertedProcessors.push(convertToPipelineInternalProcessor(processor)); + } + return convertedProcessors; +}; + +export const deserialize = ({ processors, onFailure }: DeserializeArgs): DeserializeResult => { + return { + processors: convertProcessors(processors), + onFailure: onFailure ? convertProcessors(onFailure) : undefined, + }; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts new file mode 100644 index 0000000000000..58d6e492b85e5 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { PipelineProcessorsEditor, OnUpdateHandler } from './pipeline_processors_editor.container'; + +export { OnUpdateHandlerArg } from './types'; + +export { SerializeResult } from './serialize'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.container.tsx new file mode 100644 index 0000000000000..7257677c08fc2 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.container.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useMemo } from 'react'; + +import { Processor } from '../../../../common/types'; + +import { deserialize } from './deserialize'; + +import { useProcessorsState } from './processors_reducer'; + +import { PipelineProcessorsContextProvider } from './context'; + +import { OnUpdateHandlerArg } from './types'; + +import { PipelineProcessorsEditor as PipelineProcessorsEditorUI } from './pipeline_processors_editor'; + +export interface Props { + value: { + processors: Processor[]; + onFailure?: Processor[]; + }; + onUpdate: (arg: OnUpdateHandlerArg) => void; + isTestButtonDisabled: boolean; + onTestPipelineClick: () => void; + esDocsBasePath: string; + /** + * Give users a way to react to this component opening a flyout + */ + onFlyoutOpen: () => void; +} + +export type OnUpdateHandler = (arg: OnUpdateHandlerArg) => void; + +export const PipelineProcessorsEditor: FunctionComponent = ({ + value: { processors: originalProcessors, onFailure: originalOnFailureProcessors }, + onFlyoutOpen, + onUpdate, + isTestButtonDisabled, + esDocsBasePath, + onTestPipelineClick, +}) => { + const deserializedResult = useMemo( + () => + deserialize({ + processors: originalProcessors, + onFailure: originalOnFailureProcessors, + }), + // TODO: Re-add the dependency on the props and make the state set-able + // when new props come in so that this component will be controllable + [] // eslint-disable-line react-hooks/exhaustive-deps + ); + const [processorsState, processorsDispatch] = useProcessorsState(deserializedResult); + const { processors, onFailure } = processorsState; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss new file mode 100644 index 0000000000000..ee7421d7dbfa8 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.scss @@ -0,0 +1,3 @@ +.pipelineProcessorsEditor { + margin-bottom: $euiSize; +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx new file mode 100644 index 0000000000000..b64f77f582b3a --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/pipeline_processors_editor.tsx @@ -0,0 +1,239 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FunctionComponent, useCallback, memo, useState, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSwitch } from '@elastic/eui'; + +import './pipeline_processors_editor.scss'; + +import { + ProcessorsTitleAndTestButton, + OnFailureProcessorsTitle, + ProcessorsTree, + ProcessorRemoveModal, + OnActionHandler, + OnSubmitHandler, + ProcessorSettingsForm, +} from './components'; + +import { ProcessorInternal, OnUpdateHandlerArg, FormValidityState, OnFormUpdateArg } from './types'; + +import { + ON_FAILURE_STATE_SCOPE, + PROCESSOR_STATE_SCOPE, + isOnFailureSelector, +} from './processors_reducer'; + +const PROCESSORS_BASE_SELECTOR = [PROCESSOR_STATE_SCOPE]; +const ON_FAILURE_BASE_SELECTOR = [ON_FAILURE_STATE_SCOPE]; + +import { serialize } from './serialize'; +import { getValue } from './utils'; +import { usePipelineProcessorsContext } from './context'; + +export interface Props { + processors: ProcessorInternal[]; + onFailureProcessors: ProcessorInternal[]; + onUpdate: (arg: OnUpdateHandlerArg) => void; + isTestButtonDisabled: boolean; + onTestPipelineClick: () => void; + onFlyoutOpen: () => void; +} + +export const PipelineProcessorsEditor: FunctionComponent = memo( + function PipelineProcessorsEditor({ + processors, + onFailureProcessors, + onTestPipelineClick, + isTestButtonDisabled, + onUpdate, + onFlyoutOpen, + }) { + const { + state: { editor, processorsDispatch }, + } = usePipelineProcessorsContext(); + + const { mode: editorMode, setMode: setEditorMode } = editor; + + const [formState, setFormState] = useState({ + validate: () => Promise.resolve(true), + }); + + const onFormUpdate = useCallback<(arg: OnFormUpdateArg) => void>( + ({ isValid, validate }) => { + setFormState({ + validate: async () => { + if (isValid === undefined) { + return validate(); + } + return isValid; + }, + }); + }, + [setFormState] + ); + + const [showGlobalOnFailure, setShowGlobalOnFailure] = useState( + Boolean(onFailureProcessors.length) + ); + + useEffect(() => { + onUpdate({ + validate: async () => { + const formValid = await formState.validate(); + return formValid && editorMode.id === 'idle'; + }, + getData: () => + serialize({ + onFailure: showGlobalOnFailure ? onFailureProcessors : undefined, + processors, + }), + }); + }, [processors, onFailureProcessors, onUpdate, formState, editorMode, showGlobalOnFailure]); + + const onSubmit = useCallback( + (processorTypeAndOptions) => { + switch (editorMode.id) { + case 'creatingProcessor': + processorsDispatch({ + type: 'addProcessor', + payload: { + processor: { ...processorTypeAndOptions }, + targetSelector: editorMode.arg.selector, + }, + }); + break; + case 'editingProcessor': + processorsDispatch({ + type: 'updateProcessor', + payload: { + processor: { + ...editorMode.arg.processor, + ...processorTypeAndOptions, + }, + selector: editorMode.arg.selector, + }, + }); + break; + default: + } + setEditorMode({ id: 'idle' }); + }, + [processorsDispatch, editorMode, setEditorMode] + ); + + const onCloseSettingsForm = useCallback(() => { + setEditorMode({ id: 'idle' }); + setFormState({ validate: () => Promise.resolve(true) }); + }, [setFormState, setEditorMode]); + + const onTreeAction = useCallback( + (action) => { + switch (action.type) { + case 'addProcessor': + setEditorMode({ id: 'creatingProcessor', arg: { selector: action.payload.target } }); + break; + case 'move': + setEditorMode({ id: 'idle' }); + processorsDispatch({ + type: 'moveProcessor', + payload: action.payload, + }); + break; + case 'selectToMove': + setEditorMode({ id: 'movingProcessor', arg: action.payload.info }); + break; + case 'cancelMove': + setEditorMode({ id: 'idle' }); + break; + } + }, + [processorsDispatch, setEditorMode] + ); + + const movingProcessor = editorMode.id === 'movingProcessor' ? editorMode.arg : undefined; + + return ( +
+ + + + + + + + + + + + + + + + } + checked={showGlobalOnFailure} + onChange={(e) => setShowGlobalOnFailure(e.target.checked)} + data-test-subj="pipelineEditorOnFailureToggle" + /> + + {showGlobalOnFailure ? ( + + + + ) : undefined} + + {editorMode.id === 'editingProcessor' || editorMode.id === 'creatingProcessor' ? ( + + ) : undefined} + {editorMode.id === 'removingProcessor' && ( + { + if (confirmed) { + processorsDispatch({ + type: 'removeProcessor', + payload: { selector }, + }); + } + setEditorMode({ id: 'idle' }); + }} + /> + )} +
+ ); + } +); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/constants.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/constants.ts new file mode 100644 index 0000000000000..51d5cac8ceef2 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/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 PROCESSOR_STATE_SCOPE = 'processors'; +export const ON_FAILURE_STATE_SCOPE = 'onFailure'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/index.ts new file mode 100644 index 0000000000000..7265f63f45a5d --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + State, + reducer, + useProcessorsState, + ProcessorsDispatch, + Action, +} from './processors_reducer'; + +export { ON_FAILURE_STATE_SCOPE, PROCESSOR_STATE_SCOPE } from './constants'; + +export { isChildPath, isOnFailureSelector } from './utils'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.test.ts new file mode 100644 index 0000000000000..43072d65bac4e --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.test.ts @@ -0,0 +1,376 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { reducer, State } from './processors_reducer'; +import { DropSpecialLocations } from '../constants'; +import { PARENT_CHILD_NEST_ERROR } from './utils'; + +const initialState: State = { + processors: [], + onFailure: [], + isRoot: true, +}; + +describe('Processors reducer', () => { + it('reorders processors', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors'] }, + }); + + expect(s3.processors).toEqual([processor1, processor2, processor3]); + + // Move the second processor to the first + const s4 = reducer(s3, { + type: 'moveProcessor', + payload: { + source: ['processors', '1'], + destination: ['processors', '0'], + }, + }); + + expect(s4.processors).toEqual([processor2, processor1, processor3]); + }); + + it('moves and orders processors out of lists', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + const processor4 = { id: expect.any(String), type: 'test4', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors', '1'] }, + }); + + const s4 = reducer(s3, { + type: 'addProcessor', + payload: { + processor: processor4, + targetSelector: ['processors', '1', 'onFailure', '0'], + }, + }); + + expect(s4.processors).toEqual([ + processor1, + { ...processor2, onFailure: [{ ...processor3, onFailure: [processor4] }] }, + ]); + + // Move the first on failure processor of the second processors on failure processor + // to the second position of the root level. + const s5 = reducer(s4, { + type: 'moveProcessor', + payload: { + source: ['processors', '1', 'onFailure', '0'], + destination: ['processors', '1'], + }, + }); + + expect(s5.processors).toEqual([ + processor1, + { ...processor3, onFailure: [processor4] }, + { ...processor2, onFailure: undefined }, + ]); + }); + + it('moves and orders processors into lists', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + const processor4 = { id: expect.any(String), type: 'test4', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors', '1'] }, + }); + + const s4 = reducer(s3, { + type: 'addProcessor', + payload: { + processor: processor4, + targetSelector: ['processors', '1', 'onFailure', '0'], + }, + }); + + expect(s4.processors).toEqual([ + processor1, + { ...processor2, onFailure: [{ ...processor3, onFailure: [processor4] }] }, + ]); + + // Move the first processor to the deepest most on-failure processor's failure processor + const s5 = reducer(s4, { + type: 'moveProcessor', + payload: { + source: ['processors', '0'], + destination: ['processors', '1', 'onFailure', '0', 'onFailure', '0', 'onFailure', '0'], + }, + }); + + expect(s5.processors).toEqual([ + { + ...processor2, + onFailure: [{ ...processor3, onFailure: [{ ...processor4, onFailure: [processor1] }] }], + }, + ]); + }); + + it('handles sending processor to bottom correctly', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors'] }, + }); + + // Move the parent into a child list + const s4 = reducer(s3, { + type: 'moveProcessor', + payload: { + source: ['processors', '0'], + destination: ['processors', DropSpecialLocations.bottom], + }, + }); + + // Assert nothing changed + expect(s4.processors).toEqual([processor2, processor3, processor1]); + }); + + it('will not set the root "onFailure" to "undefined" if it is empty', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['onFailure'] }, + }); + + // Move the parent into a child list + const s3 = reducer(s2, { + type: 'moveProcessor', + payload: { + source: ['onFailure', '0'], + destination: ['processors', '1'], + }, + }); + + expect(s3).toEqual({ + processors: [processor1, processor2], + onFailure: [], + isRoot: true, + }); + }); + + it('places copies and places the copied processor below the original', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + const processor4 = { + id: expect.any(String), + type: 'test4', + options: { field: 'field_name', value: 'field_value' }, + }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors', '1'] }, + }); + + const s4 = reducer(s3, { + type: 'addProcessor', + payload: { + processor: processor4, + targetSelector: ['processors', '1', 'onFailure', '0'], + }, + }); + + const s5 = reducer(s4, { + type: 'duplicateProcessor', + payload: { source: ['processors', '1', 'onFailure', '0', 'onFailure', '0'] }, + }); + + const s6 = reducer(s5, { + type: 'duplicateProcessor', + payload: { source: ['processors', '1', 'onFailure', '0', 'onFailure', '0'] }, + }); + + expect(s6.processors).toEqual([ + processor1, + { + ...processor2, + onFailure: [ + { + ...processor3, + onFailure: [processor4, processor4, processor4], + }, + ], + }, + ]); + }); + + describe('Error conditions', () => { + let originalErrorLogger: any; + beforeEach(() => { + // eslint-disable-next-line no-console + originalErrorLogger = console.error; + // eslint-disable-next-line no-console + console.error = jest.fn(); + }); + + afterEach(() => { + // eslint-disable-next-line no-console + console.error = originalErrorLogger; + }); + + it('prevents moving a parent into child list', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + const processor3 = { id: expect.any(String), type: 'test3', options: {} }; + const processor4 = { id: expect.any(String), type: 'test4', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['processors'] }, + }); + + const s3 = reducer(s2, { + type: 'addProcessor', + payload: { processor: processor3, targetSelector: ['processors', '1'] }, + }); + + const s4 = reducer(s3, { + type: 'addProcessor', + payload: { + processor: processor4, + targetSelector: ['processors', '1', 'onFailure', '0'], + }, + }); + + expect(s4.processors).toEqual([ + processor1, + { ...processor2, onFailure: [{ ...processor3, onFailure: [processor4] }] }, + ]); + + // Move the parent into a child list + const s5 = reducer(s4, { + type: 'moveProcessor', + payload: { + source: ['processors', '1'], + destination: ['processors', '1', 'onFailure', '0', 'onFailure', '0', 'onFailure', '0'], + }, + }); + + // eslint-disable-next-line no-console + expect(console.error).toHaveBeenCalledWith(new Error(PARENT_CHILD_NEST_ERROR)); + + // Assert nothing changed + expect(s5.processors).toEqual(s4.processors); + }); + + it('does not remove top level processor and onFailure arrays if they are emptied', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + const s2 = reducer(s1, { + type: 'removeProcessor', + payload: { selector: ['processors', '0'] }, + }); + expect(s2.processors).not.toBe(undefined); + }); + + it('throws for bad move processor', () => { + const processor1 = { id: expect.any(String), type: 'test1', options: {} }; + const processor2 = { id: expect.any(String), type: 'test2', options: {} }; + + const s1 = reducer(initialState, { + type: 'addProcessor', + payload: { processor: processor1, targetSelector: ['processors'] }, + }); + + const s2 = reducer(s1, { + type: 'addProcessor', + payload: { processor: processor2, targetSelector: ['onFailure'] }, + }); + + const s3 = reducer(s2, { + type: 'moveProcessor', + payload: { + source: ['onFailure'], + destination: ['processors'], + }, + }); + + // eslint-disable-next-line no-console + expect(console.error).toHaveBeenCalledWith( + new Error('Expected number but received "processors"') + ); + + expect(s3.processors).toEqual(s2.processors); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.ts new file mode 100644 index 0000000000000..4e069aab8bdd1 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/processors_reducer.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import uuid from 'uuid'; +import { Reducer, useReducer, Dispatch } from 'react'; +import { DeserializeResult } from '../deserialize'; +import { getValue, setValue } from '../utils'; +import { ProcessorInternal, ProcessorSelector } from '../types'; + +import { unsafeProcessorMove, duplicateProcessor } from './utils'; + +export type State = Omit & { + onFailure: ProcessorInternal[]; + isRoot: true; +}; + +export type Action = + | { + type: 'addProcessor'; + payload: { processor: Omit; targetSelector: ProcessorSelector }; + } + | { + type: 'updateProcessor'; + payload: { processor: ProcessorInternal; selector: ProcessorSelector }; + } + | { + type: 'removeProcessor'; + payload: { selector: ProcessorSelector }; + } + | { + type: 'moveProcessor'; + payload: { source: ProcessorSelector; destination: ProcessorSelector }; + } + | { + type: 'duplicateProcessor'; + payload: { + source: ProcessorSelector; + }; + }; + +export type ProcessorsDispatch = Dispatch; + +export const reducer: Reducer = (state, action) => { + if (action.type === 'moveProcessor') { + const { destination, source } = action.payload; + try { + return unsafeProcessorMove(state, source, destination); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + return { ...state }; + } + } + + if (action.type === 'removeProcessor') { + const { selector } = action.payload; + const processorsSelector = selector.slice(0, -1); + const parentProcessorSelector = processorsSelector.slice(0, -1); + const idx = parseInt(selector[selector.length - 1], 10); + const processors = getValue(processorsSelector, state); + processors.splice(idx, 1); + const parentProcessor = getValue(parentProcessorSelector, state); + if (!processors.length && selector.length && !(parentProcessor as State).isRoot) { + return setValue(processorsSelector, state, undefined); + } + return setValue(processorsSelector, state, [...processors]); + } + + if (action.type === 'addProcessor') { + const { processor, targetSelector } = action.payload; + if (!targetSelector.length) { + throw new Error('Expected target selector to contain a path, but received an empty array.'); + } + const targetProcessor = getValue( + targetSelector, + state + ); + if (!targetProcessor) { + throw new Error( + `Could not find processor or processors array at ${targetSelector.join('.')}` + ); + } + if (Array.isArray(targetProcessor)) { + return setValue( + targetSelector, + state, + targetProcessor.concat({ ...processor, id: uuid.v4() }) + ); + } else { + const processorWithId = { ...processor, id: uuid.v4() }; + targetProcessor.onFailure = targetProcessor.onFailure + ? targetProcessor.onFailure.concat(processorWithId) + : [processorWithId]; + return setValue(targetSelector, state, targetProcessor); + } + } + + if (action.type === 'updateProcessor') { + const { processor, selector } = action.payload; + const processorsSelector = selector.slice(0, -1); + const idx = parseInt(selector[selector.length - 1], 10); + + if (isNaN(idx)) { + throw new Error(`Expected numeric value, received ${idx}`); + } + + const processors = getValue(processorsSelector, state); + processors[idx] = processor; + return setValue(processorsSelector, state, [...processors]); + } + + if (action.type === 'duplicateProcessor') { + const sourceSelector = action.payload.source; + const sourceProcessor = getValue(sourceSelector, state); + const sourceIdx = parseInt(sourceSelector[sourceSelector.length - 1], 10); + const sourceProcessorsArraySelector = sourceSelector.slice(0, -1); + const sourceProcessorsArray = [ + ...getValue(sourceProcessorsArraySelector, state), + ]; + const copy = duplicateProcessor(sourceProcessor); + sourceProcessorsArray.splice(sourceIdx + 1, 0, copy); + return setValue(sourceProcessorsArraySelector, state, sourceProcessorsArray); + } + + return state; +}; + +export const useProcessorsState = (initialState: DeserializeResult) => { + const state = { + ...initialState, + onFailure: initialState.onFailure ?? [], + }; + return useReducer(reducer, { ...state, isRoot: true }); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/utils.ts new file mode 100644 index 0000000000000..6ee6b321f8b46 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/processors_reducer/utils.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { State } from './processors_reducer'; +import { ProcessorInternal, ProcessorSelector } from '../types'; +import { DropSpecialLocations } from '../constants'; +import { checkIfSamePath, getValue } from '../utils'; + +import { ON_FAILURE_STATE_SCOPE } from './constants'; + +/** + * We know that it must be an on-failure handler if the selector length is greater than 2 + * because the first element will always be either processors or the global on-failure + * array and the second element will be a number indicating the processor position in the + * array. Anything more than that we know we are add an on failure handler. + */ +export const isOnFailureSelector = (selector: ProcessorSelector) => + selector[0] === ON_FAILURE_STATE_SCOPE || selector.length > 2; + +export const PARENT_CHILD_NEST_ERROR = 'PARENT_CHILD_NEST_ERROR'; + +export const duplicateProcessor = (sourceProcessor: ProcessorInternal): ProcessorInternal => { + const onFailure = sourceProcessor.onFailure + ? sourceProcessor.onFailure.map((p) => duplicateProcessor(p)) + : undefined; + return { + ...sourceProcessor, + onFailure, + id: uuid.v4(), + options: { + ...sourceProcessor.options, + }, + }; +}; + +export const isChildPath = (a: ProcessorSelector, b: ProcessorSelector) => { + return a.every((pathSegment, idx) => pathSegment === b[idx]); +}; + +/** + * Unsafe! + * + * This function takes a data structure and mutates it in place. + * + * It is convenient for updating the processors (see {@link ProcessorInternal}) + * structure in this way because the structure is recursive. We are moving processors between + * different arrays, removing in one, and adding to another. The end result should be consistent + * with these actions. + * + * @remark + * This function assumes parents cannot be moved into themselves. + */ +export const unsafeProcessorMove = ( + state: State, + source: ProcessorSelector, + destination: ProcessorSelector +): State => { + const pathToSourceArray = source.slice(0, -1); + const pathToDestArray = destination.slice(0, -1); + if (isChildPath(source, destination)) { + throw new Error(PARENT_CHILD_NEST_ERROR); + } + const isXArrayMove = !checkIfSamePath(pathToSourceArray, pathToDestArray); + + // Start by setting up references to objects of interest using our selectors + // At this point, our selectors are consistent with the data passed in. + const sourceProcessors = getValue(pathToSourceArray, state); + const destinationProcessors = getValue(pathToDestArray, state); + const sourceIndex = parseInt(source[source.length - 1], 10); + const sourceProcessor = getValue(pathToSourceArray.slice(0, -1), state); + const processor = sourceProcessors[sourceIndex]; + + const lastDestItem = destination[destination.length - 1]; + let destIndex: number; + if (lastDestItem === DropSpecialLocations.top) { + destIndex = 0; + } else if (lastDestItem === DropSpecialLocations.bottom) { + destIndex = Infinity; + } else if (/^-?[0-9]+$/.test(lastDestItem)) { + destIndex = parseInt(lastDestItem, 10); + } else { + throw new Error(`Expected number but received "${lastDestItem}"`); + } + + if (isXArrayMove) { + // First perform the add operation. + if (destinationProcessors) { + destinationProcessors.splice(destIndex, 0, processor); + } else { + const targetProcessor = getValue(pathToDestArray.slice(0, -1), state); + targetProcessor.onFailure = [processor]; + } + // !! Beyond this point, selectors are no longer usable because we have mutated the data structure! + // Second, we perform the deletion operation + sourceProcessors.splice(sourceIndex, 1); + + // If onFailure is empty, delete the array. + if (!sourceProcessors.length && !((sourceProcessor as unknown) as State).isRoot) { + delete sourceProcessor.onFailure; + } + } else { + destinationProcessors.splice(destIndex, 0, processor); + const targetIdx = sourceIndex > destIndex ? sourceIndex + 1 : sourceIndex; + sourceProcessors.splice(targetIdx, 1); + } + + return { ...state, processors: [...state.processors], onFailure: [...state.onFailure] }; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/serialize.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/serialize.ts new file mode 100644 index 0000000000000..153c9e252ccc0 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/serialize.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Processor } from '../../../../common/types'; + +import { DeserializeResult } from './deserialize'; +import { ProcessorInternal } from './types'; + +type SerializeArgs = DeserializeResult; + +export interface SerializeResult { + processors: Processor[]; + on_failure?: Processor[]; +} + +const convertProcessorInternalToProcessor = (processor: ProcessorInternal): Processor => { + const { options, onFailure, type } = processor; + const outProcessor = { + [type]: { + ...options, + }, + }; + + if (onFailure?.length) { + outProcessor[type].on_failure = convertProcessors(onFailure); + } else if (onFailure) { + outProcessor[type].on_failure = []; + } + + return outProcessor; +}; + +const convertProcessors = (processors: ProcessorInternal[]) => { + const convertedProcessors = []; + + for (const processor of processors) { + convertedProcessors.push(convertProcessorInternalToProcessor(processor)); + } + return convertedProcessors; +}; + +export const serialize = ({ processors, onFailure }: SerializeArgs): SerializeResult => { + return { + processors: convertProcessors(processors), + on_failure: onFailure?.length ? convertProcessors(onFailure) : undefined, + }; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts new file mode 100644 index 0000000000000..aa39fca29fa8b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { OnFormUpdateArg } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +import { SerializeResult } from './serialize'; +import { ProcessorInfo } from './components/processors_tree'; + +/** + * An array of keys that map to a value in an object + * structure. + * + * For instance: + * ['a', 'b', '0', 'c'] given { a: { b: [ { c: [] } ] } } => [] + * + * Additionally, an empty selector `[]`, is a special indicator + * for the root level. + */ +export type ProcessorSelector = string[]; + +/** @private */ +export interface ProcessorInternal { + id: string; + type: string; + options: { [key: string]: any }; + onFailure?: ProcessorInternal[]; +} + +export { OnFormUpdateArg }; + +export interface FormValidityState { + validate: OnFormUpdateArg['validate']; +} + +export interface OnUpdateHandlerArg extends FormValidityState { + getData: () => SerializeResult; +} + +/** + * The editor can be in different modes. This enables us to hold + * a reference to data dispatch to the reducer (like the {@link ProcessorSelector} + * which will be used to update the in-memory processors data structure. + */ +export type EditorMode = + | { id: 'creatingProcessor'; arg: { selector: ProcessorSelector } } + | { id: 'movingProcessor'; arg: ProcessorInfo } + | { id: 'editingProcessor'; arg: { processor: ProcessorInternal; selector: ProcessorSelector } } + | { id: 'removingProcessor'; arg: { selector: ProcessorSelector } } + | { id: 'idle' }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.test.ts new file mode 100644 index 0000000000000..0b7620f517161 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getValue, setValue } from './utils'; + +describe('get and set values', () => { + const testObject = Object.freeze([{ onFailure: [{ onFailure: 1 }] }]); + describe('#getValue', () => { + it('gets a deeply nested value', () => { + expect(getValue(['0', 'onFailure', '0', 'onFailure'], testObject)).toBe(1); + }); + + it('empty array for path returns "root" value', () => { + const result = getValue([], testObject); + expect(result).toEqual(testObject); + // Getting does not create a copy + expect(result).toBe(testObject); + }); + }); + + describe('#setValue', () => { + it('sets a deeply nested value', () => { + const result = setValue(['0', 'onFailure', '0', 'onFailure'], testObject, 2); + expect(result).toEqual([{ onFailure: [{ onFailure: 2 }] }]); + expect(result).not.toBe(testObject); + }); + + it('returns value if no path was provided', () => { + setValue([], testObject, 2); + expect(testObject).toEqual([{ onFailure: [{ onFailure: 1 }] }]); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts new file mode 100644 index 0000000000000..49d24e8dc35c3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ProcessorSelector } from './types'; + +type Path = string[]; + +/** + * The below get and set functions are built with an API to make setting + * and getting and setting values more simple. + * + * @remark + * NEVER use these with objects that contain keys created by user input. + */ + +/** + * Given a path, get the value at the path + * + * @remark + * If path is an empty array, return the source. + */ +export const getValue = (path: Path, source: any) => { + let current = source; + for (const key of path) { + current = (current as any)[key]; + } + return (current as unknown) as Result; +}; + +const ARRAY_TYPE = Object.prototype.toString.call([]); +const OBJECT_TYPE = Object.prototype.toString.call({}); + +const dumbCopy = (value: R): R => { + const objectType = Object.prototype.toString.call(value); + if (objectType === ARRAY_TYPE) { + return ([...(value as any)] as unknown) as R; + } else if (objectType === OBJECT_TYPE) { + return { ...(value as any) } as R; + } + + throw new Error(`Expected (${ARRAY_TYPE}|${OBJECT_TYPE}) but received ${objectType}`); +}; + +const WHITELISTED_KEYS_REGEX = /^([0-9]+|onFailure|processors)$/; +/** + * Given a path, value and an object (array or object) set + * the value at the path and copy objects values on the + * path only. This is a partial copy mechanism that is best + * effort for providing state updates to the UI, could break down + * if other updates are made to non-copied parts of state in external + * references - but this should not happen. + * + * @remark + * If path is empty, just shallow copy source. + */ +export const setValue = ( + path: Path, + source: Target, + value: Value +): Target => { + if (!path.length) { + return dumbCopy(source); + } + + let current: any; + let result: Target; + + for (let idx = 0; idx < path.length; ++idx) { + const key = path[idx]; + if (!WHITELISTED_KEYS_REGEX.test(key)) { + // eslint-disable-next-line no-console + console.error( + `Received non-whitelisted key "${key}". Aborting set value operation; returning original.` + ); + return dumbCopy(source); + } + const atRoot = !current; + + if (atRoot) { + result = dumbCopy(source); + current = result; + } + + if (idx + 1 === path.length) { + current[key] = value; + } else { + current[key] = dumbCopy(current[key]); + current = current[key]; + } + } + + return result!; +}; + +export const checkIfSamePath = (pathA: ProcessorSelector, pathB: ProcessorSelector) => { + if (pathA.length !== pathB.length) return false; + return pathA.join('.') === pathB.join('.'); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx index 97775965f9b45..0803b419bdbe4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx @@ -89,7 +89,7 @@ export const PipelineTable: FunctionComponent = ({ {...reactRouterNavigate(history, '/create')} > {i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { - defaultMessage: 'Create a pipeline here', + defaultMessage: 'Create a pipeline', })} , ], diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts index 05fdc4b1dfb84..7f6a87a46fea3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/documentation.ts @@ -15,6 +15,10 @@ export class DocumentationService { this.esDocBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; } + public getEsDocsBasePath() { + return this.esDocBasePath; + } + public getIngestNodeUrl() { return `${this.esDocBasePath}/ingest.html`; } diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index ab56ae427120b..9ddb953c71978 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -29,7 +29,13 @@ export { Form, getUseField, ValidationFuncArg, + FormData, + UseField, + FormHook, useFormContext, + FormDataProvider, + OnFormUpdateArg, + FieldConfig, } from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; export { @@ -41,6 +47,9 @@ export { getFormRow, Field, JsonEditorField, + FormRow, + ToggleField, + ComboBoxField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index a8b22b3e22750..346a5a24c269f 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -13,5 +13,6 @@ "dashboard" ], "optionalPlugins": ["embeddable", "usageCollection", "taskManager", "uiActions"], - "configPath": ["xpack", "lens"] + "configPath": ["xpack", "lens"], + "extraPublicDirs": ["common/constants"] } diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap index f8f467b25643b..fc5ed7480dd1f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/xy_expression.test.tsx.snap @@ -4,7 +4,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - { // TODO: Is it expected behavior for it not to auto-generate a uui or throw // error if item_id is not passed in? - xtest('it should accept an undefined for "item_id" and auto generate a uuid', () => { + test.skip('it should accept an undefined for "item_id" and auto generate a uuid', () => { const inputPayload = getUpdateExceptionListItemSchemaMock(); delete inputPayload.item_id; const decoded = updateExceptionListItemSchema.decode(inputPayload); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts index b9d142fbccbee..ff900104251b7 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.test.ts @@ -101,7 +101,7 @@ describe('exception_list_item_schema', () => { // TODO: Should this throw an error? "namespace_type" gets auto-populated // with default "single", is that desired behavior? - xtest('it should NOT accept an undefined for "namespace_type"', () => { + test.skip('it should NOT accept an undefined for "namespace_type"', () => { const payload = getExceptionListItemSchemaMock(); delete payload.namespace_type; const decoded = exceptionListItemSchema.decode(payload); diff --git a/x-pack/plugins/lists/common/siem_common_deps.ts b/x-pack/plugins/lists/common/siem_common_deps.ts index 3759305987f79..b1bb7d8aace36 100644 --- a/x-pack/plugins/lists/common/siem_common_deps.ts +++ b/x-pack/plugins/lists/common/siem_common_deps.ts @@ -9,3 +9,5 @@ export { DefaultUuid } from '../../security_solution/common/detection_engine/sch export { DefaultStringArray } from '../../security_solution/common/detection_engine/schemas/types/default_string_array'; export { exactCheck } from '../../security_solution/common/exact_check'; export { getPaths, foldLeftRight } from '../../security_solution/common/test_utils'; +export { validate } from '../../security_solution/common/validate'; +export { formatErrors } from '../../security_solution/common/format_errors'; diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts index 2cafd435e0853..375d25c6fa5f8 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { CreateExceptionListItemSchemaDecoded, createExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts index 9be6b72dcd255..bd29a65c9450a 100644 --- a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { CreateExceptionListSchemaDecoded, createExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/create_list_index_route.ts index 1c893fb757c5d..5ec2b36da61b0 100644 --- a/x-pack/plugins/lists/server/routes/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_index_route.ts @@ -6,7 +6,8 @@ import { IRouter } from 'kibana/server'; -import { buildSiemResponse, transformError, validate } from '../siem_server_deps'; +import { buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { LIST_INDEX } from '../../common/constants'; import { acknowledgeSchema } from '../../common/schemas'; diff --git a/x-pack/plugins/lists/server/routes/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_list_item_route.ts index 68622e98cbc52..2e1b7fa07221f 100644 --- a/x-pack/plugins/lists/server/routes/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_item_route.ts @@ -7,13 +7,9 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { createListItemSchema, listItemSchema } from '../../common/schemas'; +import { validate } from '../../common/siem_common_deps'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/create_list_route.ts b/x-pack/plugins/lists/server/routes/create_list_route.ts index 0f3c404c53cfd..9872bbfa09e23 100644 --- a/x-pack/plugins/lists/server/routes/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { createListSchema, listSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts index 2c91fe3c28681..f363252dada50 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { DeleteExceptionListItemSchemaDecoded, deleteExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts index b4c67c0ab1418..b1bf705dcc5f6 100644 --- a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { DeleteExceptionListSchemaDecoded, deleteExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts index 424c3f45aac40..cb2e16b3602a7 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_index_route.ts @@ -7,7 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_INDEX } from '../../common/constants'; -import { buildSiemResponse, transformError, validate } from '../siem_server_deps'; +import { buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { acknowledgeSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index 82dfe8a4f29d0..510be764cefba 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { deleteListItemSchema, listItemArraySchema, listItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/delete_list_route.ts b/x-pack/plugins/lists/server/routes/delete_list_route.ts index e89355b7689c5..600e4b00c29ca 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { deleteListSchema, listSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts index 1820ffdeadb88..a6c2a18bb8c8a 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { FindExceptionListItemSchemaDecoded, findExceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts index 3181deda8b91d..97e1de834cd37 100644 --- a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { FindExceptionListSchemaDecoded, findExceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_list_item_route.ts index 37b5fe44b919c..1ccb948d0ad21 100644 --- a/x-pack/plugins/lists/server/routes/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { findListItemSchema, foundListItemSchema } from '../../common/schemas'; import { decodeCursor } from '../services/utils'; diff --git a/x-pack/plugins/lists/server/routes/find_list_route.ts b/x-pack/plugins/lists/server/routes/find_list_route.ts index 04b33e3d67075..2fa43c6368b5c 100644 --- a/x-pack/plugins/lists/server/routes/find_list_route.ts +++ b/x-pack/plugins/lists/server/routes/find_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { findListSchema, foundListSchema } from '../../common/schemas'; import { decodeCursor } from '../services/utils'; diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index c951c9b337131..67f345c2c6c1d 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -9,12 +9,8 @@ import { Readable } from 'stream'; import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { importListItemQuerySchema, importListItemSchema, listSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts index e18fd0618b133..f706559dffdbd 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listItemSchema, patchListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/patch_list_route.ts b/x-pack/plugins/lists/server/routes/patch_list_route.ts index 9d3fa4db8ccd0..3a0d8714a14cd 100644 --- a/x-pack/plugins/lists/server/routes/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/patch_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listSchema, patchListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts index 083d4d7a0d479..c4e969b27fcf4 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { ReadExceptionListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts index c295f045b38c2..6cb91c10aea55 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { ReadExceptionListSchemaDecoded, exceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/read_list_index_route.ts index 21f539d97fc74..4664bed3e7a8b 100644 --- a/x-pack/plugins/lists/server/routes/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_index_route.ts @@ -7,7 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_INDEX } from '../../common/constants'; -import { buildSiemResponse, transformError, validate } from '../siem_server_deps'; +import { buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listItemIndexExistSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 10c7f781f554c..24011d3b50d27 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -7,13 +7,9 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; import { listItemArraySchema, listItemSchema, readListItemSchema } from '../../common/schemas'; +import { validate } from '../../common/siem_common_deps'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/read_list_route.ts b/x-pack/plugins/lists/server/routes/read_list_route.ts index c30eadfca0b65..34924b70fd4df 100644 --- a/x-pack/plugins/lists/server/routes/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listSchema, readListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts index 73392c326056e..0ec33b7651982 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { UpdateExceptionListItemSchemaDecoded, exceptionListItemSchema, diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts index fe45d403c040f..cff78614d05ba 100644 --- a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { EXCEPTION_LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { UpdateExceptionListSchemaDecoded, exceptionListSchema, diff --git a/x-pack/plugins/lists/server/routes/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_list_item_route.ts index 494d57b93b8e4..3e231e319104b 100644 --- a/x-pack/plugins/lists/server/routes/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_item_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listItemSchema, updateListItemSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/routes/update_list_route.ts b/x-pack/plugins/lists/server/routes/update_list_route.ts index 6ace61e46a780..a6d9f8329c7c8 100644 --- a/x-pack/plugins/lists/server/routes/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/update_list_route.ts @@ -7,12 +7,8 @@ import { IRouter } from 'kibana/server'; import { LIST_URL } from '../../common/constants'; -import { - buildRouteValidation, - buildSiemResponse, - transformError, - validate, -} from '../siem_server_deps'; +import { buildRouteValidation, buildSiemResponse, transformError } from '../siem_server_deps'; +import { validate } from '../../common/siem_common_deps'; import { listSchema, updateListSchema } from '../../common/schemas'; import { getListClient } from '.'; diff --git a/x-pack/plugins/lists/server/siem_server_deps.ts b/x-pack/plugins/lists/server/siem_server_deps.ts index df4b07fc46322..87a623c7a1892 100644 --- a/x-pack/plugins/lists/server/siem_server_deps.ts +++ b/x-pack/plugins/lists/server/siem_server_deps.ts @@ -17,5 +17,4 @@ export { createBootstrapIndex, getIndexExists, buildRouteValidation, - validate, } from '../../security_solution/server'; diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index ad99780a7d32f..edb395633827f 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -227,3 +227,9 @@ export enum INITIAL_LOCATION { FIXED_LOCATION = 'FIXED_LOCATION', BROWSER_LOCATION = 'BROWSER_LOCATION', } + +export enum LAYER_WIZARD_CATEGORY { + ELASTICSEARCH = 'ELASTICSEARCH', + REFERENCE = 'REFERENCE', + SOLUTIONS = 'SOLUTIONS', +} diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 67520321de761..f8a30b8d0337e 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -18,5 +18,6 @@ "usageCollection" ], "ui": true, - "server": true + "server": true, + "extraPublicDirs": ["common/constants"] } diff --git a/x-pack/plugins/maps/public/api/create_security_layer_descriptors.ts b/x-pack/plugins/maps/public/api/create_security_layer_descriptors.ts new file mode 100644 index 0000000000000..afff92a584b3c --- /dev/null +++ b/x-pack/plugins/maps/public/api/create_security_layer_descriptors.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 { LayerDescriptor } from '../../common/descriptor_types'; +import { lazyLoadMapModules } from '../lazy_load_bundle'; + +export async function createSecurityLayerDescriptors( + indexPatternId: string, + indexPatternTitle: string +): Promise { + const mapModules = await lazyLoadMapModules(); + return mapModules.createSecurityLayerDescriptors(indexPatternId, indexPatternTitle); +} diff --git a/x-pack/plugins/maps/public/api/index.ts b/x-pack/plugins/maps/public/api/index.ts new file mode 100644 index 0000000000000..8b45d31b41d44 --- /dev/null +++ b/x-pack/plugins/maps/public/api/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 { MapsStartApi } from './start_api'; diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts new file mode 100644 index 0000000000000..d45b0df63c839 --- /dev/null +++ b/x-pack/plugins/maps/public/api/start_api.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 { LayerDescriptor } from '../../common/descriptor_types'; + +export interface MapsStartApi { + createSecurityLayerDescriptors: ( + indexPatternId: string, + indexPatternTitle: string + ) => Promise; +} diff --git a/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts index 2bdeb6446cf28..a255ffb00e312 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts @@ -7,6 +7,7 @@ import { ReactElement } from 'react'; import { LayerDescriptor } from '../../../common/descriptor_types'; +import { LAYER_WIZARD_CATEGORY } from '../../../common/constants'; export type RenderWizardArguments = { previewLayers: (layerDescriptors: LayerDescriptor[], isIndexingSource?: boolean) => void; @@ -20,6 +21,7 @@ export type RenderWizardArguments = { }; export type LayerWizard = { + categories: LAYER_WIZARD_CATEGORY[]; checkVisibility?: () => Promise; description: string; icon: string; diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index 8b6f0a0f3f223..8357971a3778f 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -25,6 +25,7 @@ import { tmsLayerWizardConfig } from '../sources/xyz_tms_source'; import { wmsLayerWizardConfig } from '../sources/wms_source'; import { mvtVectorSourceWizardConfig } from '../sources/mvt_single_layer_vector_source'; import { ObservabilityLayerWizardConfig } from './solution_layers/observability'; +import { SecurityLayerWizardConfig } from './solution_layers/security'; import { getEnableVectorTiles } from '../../kibana_services'; let registered = false; @@ -36,6 +37,7 @@ export function registerLayerWizards() { // Registration order determines display order registerLayerWizard(uploadLayerWizardConfig); registerLayerWizard(ObservabilityLayerWizardConfig); + registerLayerWizard(SecurityLayerWizardConfig); // @ts-ignore registerLayerWizard(esDocumentsLayerWizardConfig); // @ts-ignore diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index ba019f97b287f..85601cfc17e8f 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -40,6 +40,7 @@ import { getDefaultDynamicProperties } from '../../../styles/vector/vector_style // redefining APM constant to avoid making maps app depend on APM plugin export const APM_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; +export const APM_INDEX_PATTERN_TITLE = 'apm-*'; const defaultDynamicProperties = getDefaultDynamicProperties(); @@ -173,7 +174,7 @@ export function createLayerDescriptor({ type: SOURCE_TYPES.ES_TERM_SOURCE, id: joinId, indexPatternId: APM_INDEX_PATTERN_ID, - indexPatternTitle: 'apm-*', // TODO look up from APM_OSS.indexPattern + indexPatternTitle: APM_INDEX_PATTERN_TITLE, // TODO look up from APM_OSS.indexPattern term: 'client.geo.country_iso_code', metrics: [metricsDescriptor], whereQuery: apmSourceQuery, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/index.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/index.ts index ae6ade86de980..abe4eccb91e66 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/index.ts @@ -5,3 +5,4 @@ */ export { ObservabilityLayerWizardConfig } from './observability_layer_wizard'; +export { APM_INDEX_PATTERN_TITLE } from './create_layer_descriptor'; diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_wizard.tsx index db97c08596e06..ddb07a9facee7 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/observability_layer_wizard.tsx @@ -6,12 +6,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { LAYER_WIZARD_CATEGORY } from '../../../../../common/constants'; import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; import { ObservabilityLayerTemplate } from './observability_layer_template'; import { APM_INDEX_PATTERN_ID } from './create_layer_descriptor'; import { getIndexPatternService } from '../../../../kibana_services'; export const ObservabilityLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH, LAYER_WIZARD_CATEGORY.SOLUTIONS], checkVisibility: async () => { try { await getIndexPatternService().get(APM_INDEX_PATTERN_ID); diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts new file mode 100644 index 0000000000000..49a86f45a681b --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.test.ts @@ -0,0 +1,688 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../../../../kibana_services', () => { + const mockUiSettings = { + get: () => { + return undefined; + }, + }; + return { + getUiSettings: () => { + return mockUiSettings; + }, + }; +}); + +jest.mock('uuid/v4', () => { + return function () { + return '12345'; + }; +}); + +import { createSecurityLayerDescriptors } from './create_layer_descriptors'; + +describe('createLayerDescriptor', () => { + test('amp index', () => { + expect(createSecurityLayerDescriptors('id', 'apm-*-transaction*')).toEqual([ + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'apm-*-transaction* | Source Point', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + filterByMapBounds: true, + geoField: 'client.geo.location', + id: '12345', + indexPatternId: 'id', + scalingType: 'TOP_HITS', + sortField: '', + sortOrder: 'desc', + tooltipProperties: [ + 'host.name', + 'client.ip', + 'client.domain', + 'client.geo.country_iso_code', + 'client.as.organization.name', + ], + topHitsSize: 1, + topHitsSplitField: 'client.ip', + type: 'ES_SEARCH', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#6092C0', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'home', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 8, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + size: 2, + }, + type: 'STATIC', + }, + symbolizeAs: { + options: { + value: 'icon', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'apm-*-transaction* | Destination point', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + filterByMapBounds: true, + geoField: 'server.geo.location', + id: '12345', + indexPatternId: 'id', + scalingType: 'TOP_HITS', + sortField: '', + sortOrder: 'desc', + tooltipProperties: [ + 'host.name', + 'server.ip', + 'server.domain', + 'server.geo.country_iso_code', + 'server.as.organization.name', + ], + topHitsSize: 1, + topHitsSplitField: 'server.ip', + type: 'ES_SEARCH', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#D36086', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'marker', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 8, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + size: 2, + }, + type: 'STATIC', + }, + symbolizeAs: { + options: { + value: 'icon', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'apm-*-transaction* | Line', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + destGeoField: 'server.geo.location', + id: '12345', + indexPatternId: 'id', + metrics: [ + { + field: 'client.bytes', + type: 'sum', + }, + { + field: 'server.bytes', + type: 'sum', + }, + ], + sourceGeoField: 'client.geo.location', + type: 'ES_PEW_PEW', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#54B399', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'marker', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 6, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#6092C0', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + field: { + name: 'doc_count', + origin: 'source', + }, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + maxSize: 8, + minSize: 1, + }, + type: 'DYNAMIC', + }, + symbolizeAs: { + options: { + value: 'circle', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + ]); + }); + + test('non-apm index', () => { + expect(createSecurityLayerDescriptors('id', 'filebeat-*')).toEqual([ + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'filebeat-* | Source Point', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + filterByMapBounds: true, + geoField: 'source.geo.location', + id: '12345', + indexPatternId: 'id', + scalingType: 'TOP_HITS', + sortField: '', + sortOrder: 'desc', + tooltipProperties: [ + 'host.name', + 'source.ip', + 'source.domain', + 'source.geo.country_iso_code', + 'source.as.organization.name', + ], + topHitsSize: 1, + topHitsSplitField: 'source.ip', + type: 'ES_SEARCH', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#6092C0', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'home', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 8, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + size: 2, + }, + type: 'STATIC', + }, + symbolizeAs: { + options: { + value: 'icon', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'filebeat-* | Destination point', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + filterByMapBounds: true, + geoField: 'destination.geo.location', + id: '12345', + indexPatternId: 'id', + scalingType: 'TOP_HITS', + sortField: '', + sortOrder: 'desc', + tooltipProperties: [ + 'host.name', + 'destination.ip', + 'destination.domain', + 'destination.geo.country_iso_code', + 'destination.as.organization.name', + ], + topHitsSize: 1, + topHitsSplitField: 'destination.ip', + type: 'ES_SEARCH', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#D36086', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'marker', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 8, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + size: 2, + }, + type: 'STATIC', + }, + symbolizeAs: { + options: { + value: 'icon', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + { + __dataRequests: [], + alpha: 0.75, + id: '12345', + joins: [], + label: 'filebeat-* | Line', + maxZoom: 24, + minZoom: 0, + sourceDescriptor: { + destGeoField: 'destination.geo.location', + id: '12345', + indexPatternId: 'id', + metrics: [ + { + field: 'source.bytes', + type: 'sum', + }, + { + field: 'destination.bytes', + type: 'sum', + }, + ], + sourceGeoField: 'source.geo.location', + type: 'ES_PEW_PEW', + }, + style: { + isTimeAware: true, + properties: { + fillColor: { + options: { + color: '#54B399', + }, + type: 'STATIC', + }, + icon: { + options: { + value: 'marker', + }, + type: 'STATIC', + }, + iconOrientation: { + options: { + orientation: 0, + }, + type: 'STATIC', + }, + iconSize: { + options: { + size: 6, + }, + type: 'STATIC', + }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, + labelColor: { + options: { + color: '#000000', + }, + type: 'STATIC', + }, + labelSize: { + options: { + size: 14, + }, + type: 'STATIC', + }, + labelText: { + options: { + value: '', + }, + type: 'STATIC', + }, + lineColor: { + options: { + color: '#6092C0', + }, + type: 'STATIC', + }, + lineWidth: { + options: { + field: { + name: 'doc_count', + origin: 'source', + }, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + maxSize: 8, + minSize: 1, + }, + type: 'DYNAMIC', + }, + symbolizeAs: { + options: { + value: 'circle', + }, + }, + }, + type: 'VECTOR', + }, + type: 'VECTOR', + visible: true, + }, + ]); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts new file mode 100644 index 0000000000000..909cd93b3df7a --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import minimatch from 'minimatch'; +import { i18n } from '@kbn/i18n'; +import { euiPaletteColorBlind } from '@elastic/eui'; +import { + LayerDescriptor, + SizeDynamicOptions, + VectorStylePropertiesDescriptor, +} from '../../../../../common/descriptor_types'; +import { + AGG_TYPE, + COUNT_PROP_NAME, + FIELD_ORIGIN, + SCALING_TYPES, + STYLE_TYPE, + SYMBOLIZE_AS_TYPES, + VECTOR_STYLES, +} from '../../../../../common/constants'; +import { VectorLayer } from '../../vector_layer/vector_layer'; +import { VectorStyle } from '../../../styles/vector/vector_style'; +// @ts-ignore +import { ESSearchSource } from '../../../sources/es_search_source'; +// @ts-ignore +import { ESPewPewSource } from '../../../sources/es_pew_pew_source'; +import { getDefaultDynamicProperties } from '../../../styles/vector/vector_style_defaults'; +import { APM_INDEX_PATTERN_TITLE } from '../observability'; + +const defaultDynamicProperties = getDefaultDynamicProperties(); +const euiVisColorPalette = euiPaletteColorBlind(); + +function isApmIndex(indexPatternTitle: string) { + return minimatch(indexPatternTitle, APM_INDEX_PATTERN_TITLE); +} + +function getSourceField(indexPatternTitle: string) { + return isApmIndex(indexPatternTitle) ? 'client.geo.location' : 'source.geo.location'; +} + +function getDestinationField(indexPatternTitle: string) { + return isApmIndex(indexPatternTitle) ? 'server.geo.location' : 'destination.geo.location'; +} + +function createSourceLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { + const sourceDescriptor = ESSearchSource.createDescriptor({ + indexPatternId, + geoField: getSourceField(indexPatternTitle), + scalingType: SCALING_TYPES.TOP_HITS, + topHitsSplitField: isApmIndex(indexPatternTitle) ? 'client.ip' : 'source.ip', + tooltipProperties: isApmIndex(indexPatternTitle) + ? [ + 'host.name', + 'client.ip', + 'client.domain', + 'client.geo.country_iso_code', + 'client.as.organization.name', + ] + : [ + 'host.name', + 'source.ip', + 'source.domain', + 'source.geo.country_iso_code', + 'source.as.organization.name', + ], + }); + + const styleProperties: VectorStylePropertiesDescriptor = { + [VECTOR_STYLES.FILL_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { color: euiVisColorPalette[1] }, + }, + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { color: '#FFFFFF' }, + }, + [VECTOR_STYLES.LINE_WIDTH]: { type: STYLE_TYPE.STATIC, options: { size: 2 } }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { value: SYMBOLIZE_AS_TYPES.ICON }, + }, + [VECTOR_STYLES.ICON]: { + type: STYLE_TYPE.STATIC, + options: { value: 'home' }, + }, + [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.STATIC, options: { size: 8 } }, + }; + + return VectorLayer.createDescriptor({ + label: i18n.translate('xpack.maps.sescurity.sourceLayerLabel', { + defaultMessage: '{indexPatternTitle} | Source Point', + values: { indexPatternTitle }, + }), + sourceDescriptor, + style: VectorStyle.createDescriptor(styleProperties), + }); +} + +function createDestinationLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { + const sourceDescriptor = ESSearchSource.createDescriptor({ + indexPatternId, + geoField: getDestinationField(indexPatternTitle), + scalingType: SCALING_TYPES.TOP_HITS, + topHitsSplitField: isApmIndex(indexPatternTitle) ? 'server.ip' : 'destination.ip', + tooltipProperties: isApmIndex(indexPatternTitle) + ? [ + 'host.name', + 'server.ip', + 'server.domain', + 'server.geo.country_iso_code', + 'server.as.organization.name', + ] + : [ + 'host.name', + 'destination.ip', + 'destination.domain', + 'destination.geo.country_iso_code', + 'destination.as.organization.name', + ], + }); + + const styleProperties: VectorStylePropertiesDescriptor = { + [VECTOR_STYLES.FILL_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { color: euiVisColorPalette[2] }, + }, + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { color: '#FFFFFF' }, + }, + [VECTOR_STYLES.LINE_WIDTH]: { type: STYLE_TYPE.STATIC, options: { size: 2 } }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { value: SYMBOLIZE_AS_TYPES.ICON }, + }, + [VECTOR_STYLES.ICON]: { + type: STYLE_TYPE.STATIC, + options: { value: 'marker' }, + }, + [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.STATIC, options: { size: 8 } }, + }; + + return VectorLayer.createDescriptor({ + label: i18n.translate('xpack.maps.sescurity.destinationLayerLabel', { + defaultMessage: '{indexPatternTitle} | Destination point', + values: { indexPatternTitle }, + }), + sourceDescriptor, + style: VectorStyle.createDescriptor(styleProperties), + }); +} + +function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { + const sourceDescriptor = ESPewPewSource.createDescriptor({ + indexPatternId, + sourceGeoField: getSourceField(indexPatternTitle), + destGeoField: getDestinationField(indexPatternTitle), + metrics: [ + { + type: AGG_TYPE.SUM, + field: isApmIndex(indexPatternTitle) ? 'client.bytes' : 'source.bytes', + }, + { + type: AGG_TYPE.SUM, + field: isApmIndex(indexPatternTitle) ? 'server.bytes' : 'destination.bytes', + }, + ], + }); + + const styleProperties: VectorStylePropertiesDescriptor = { + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { color: euiVisColorPalette[1] }, + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH]!.options as SizeDynamicOptions), + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + minSize: 1, + maxSize: 8, + }, + }, + }; + + return VectorLayer.createDescriptor({ + label: i18n.translate('xpack.maps.sescurity.lineLayerLabel', { + defaultMessage: '{indexPatternTitle} | Line', + values: { indexPatternTitle }, + }), + sourceDescriptor, + style: VectorStyle.createDescriptor(styleProperties), + }); +} + +export function createSecurityLayerDescriptors( + indexPatternId: string, + indexPatternTitle: string +): LayerDescriptor[] { + return [ + createSourceLayerDescriptor(indexPatternId, indexPatternTitle), + createDestinationLayerDescriptor(indexPatternId, indexPatternTitle), + createLineLayerDescriptor(indexPatternId, indexPatternTitle), + ]; +} diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/index.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/index.ts new file mode 100644 index 0000000000000..f0cc4f26fc1dd --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/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 { createSecurityLayerDescriptors } from './create_layer_descriptors'; +export { SecurityLayerWizardConfig } from './security_layer_wizard'; diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/index_pattern_select.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/index_pattern_select.tsx new file mode 100644 index 0000000000000..1e4d9a57a336c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/index_pattern_select.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ChangeEvent, Component } from 'react'; +import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { getSecurityIndexPatterns, IndexPatternMeta } from './security_index_pattern_utils'; + +interface Props { + value: string; + onChange: (indexPatternMeta: IndexPatternMeta | null) => void; +} + +interface State { + hasLoaded: boolean; + options: EuiSelectOption[]; +} + +export class IndexPatternSelect extends Component { + private _isMounted: boolean = false; + + state = { + hasLoaded: false, + options: [], + }; + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadOptions(); + } + + async _loadOptions() { + const indexPatterns = await getSecurityIndexPatterns(); + if (!this._isMounted) { + return; + } + + this.setState({ + hasLoaded: true, + options: [ + { value: '', text: '' }, + ...indexPatterns.map(({ id, title }: IndexPatternMeta) => { + return { + value: id, + text: title, + }; + }), + ], + }); + } + + _onChange = (event: ChangeEvent) => { + const targetOption = this.state.options.find(({ value, text }: EuiSelectOption) => { + return event.target.value === value; + }); + + if (event.target.value === '' || !targetOption) { + this.props.onChange(null); + return; + } + + this.props.onChange({ + // @ts-expect-error - avoid wrong "Property does not exist on type 'never'." compile error + id: targetOption.value, + // @ts-expect-error - avoid wrong "Property does not exist on type 'never'." compile error + title: targetOption.text, + }); + }; + + render() { + if (!this.state.hasLoaded) { + return null; + } + + return ( + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_index_pattern_utils.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_index_pattern_utils.ts new file mode 100644 index 0000000000000..141b9133505b7 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_index_pattern_utils.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. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import minimatch from 'minimatch'; +import { SimpleSavedObject } from 'src/core/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IndexPatternSavedObjectAttrs } from 'src/plugins/data/common/index_patterns/index_patterns/index_patterns'; +import { getIndexPatternService, getUiSettings } from '../../../../kibana_services'; + +export type IndexPatternMeta = { + id: string; + title: string; +}; + +export async function getSecurityIndexPatterns(): Promise { + const uiSettings = getUiSettings(); + let securityIndexPatternTitles: string[]; + try { + securityIndexPatternTitles = uiSettings.get('securitySolution:defaultIndex'); + } catch (error) { + // UiSettings throws with unreconized configuration setting + // siem:defaultIndex configuration setting is not registered if security app is not running + return []; + } + + const indexPatternCache = await getIndexPatternService().getCache(); + return indexPatternCache! + .filter((savedObject: SimpleSavedObject) => { + return (securityIndexPatternTitles as string[]).some((indexPatternTitle) => { + // glob matching index pattern title + return minimatch(indexPatternTitle, savedObject?.attributes?.title); + }); + }) + .map((savedObject: SimpleSavedObject) => { + return { + id: savedObject.id, + title: savedObject.attributes.title, + }; + }); +} diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx new file mode 100644 index 0000000000000..eda489c88fda2 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_template.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { RenderWizardArguments } from '../../layer_wizard_registry'; +import { IndexPatternSelect } from './index_pattern_select'; +import { createSecurityLayerDescriptors } from './create_layer_descriptors'; +import { IndexPatternMeta } from './security_index_pattern_utils'; + +interface State { + indexPatternId: string | null; + indexPatternTitle: string | null; +} + +export class SecurityLayerTemplate extends Component { + state = { + indexPatternId: null, + indexPatternTitle: null, + }; + + _onIndexPatternChange = (indexPatternMeta: IndexPatternMeta | null) => { + this.setState( + { + indexPatternId: indexPatternMeta ? indexPatternMeta.id : null, + indexPatternTitle: indexPatternMeta ? indexPatternMeta.title : null, + }, + this._previewLayer + ); + }; + + _previewLayer() { + if (!this.state.indexPatternId || !this.state.indexPatternTitle) { + this.props.previewLayers([]); + return; + } + + this.props.previewLayers( + createSecurityLayerDescriptors(this.state.indexPatternId!, this.state.indexPatternTitle!) + ); + } + + render() { + return ( + + + + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_wizard.tsx new file mode 100644 index 0000000000000..f51aa5b40aa80 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/security_layer_wizard.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { LAYER_WIZARD_CATEGORY } from '../../../../../common/constants'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +import { getSecurityIndexPatterns } from './security_index_pattern_utils'; +import { SecurityLayerTemplate } from './security_layer_template'; + +export const SecurityLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH, LAYER_WIZARD_CATEGORY.SOLUTIONS], + checkVisibility: async () => { + const indexPatterns = await getSecurityIndexPatterns(); + return indexPatterns.length > 0; + }, + description: i18n.translate('xpack.maps.security.desc', { + defaultMessage: 'Security layers', + }), + icon: 'logoSecurity', + renderWizard: (renderWizardArguments: RenderWizardArguments) => { + return ; + }, + title: i18n.translate('xpack.maps.security.title', { + defaultMessage: 'Security', + }), +}; diff --git a/x-pack/plugins/maps/public/classes/sources/client_file_source/upload_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/client_file_source/upload_layer_wizard.tsx index 3f4ec0d3f1268..0a224f75b981d 100644 --- a/x-pack/plugins/maps/public/classes/sources/client_file_source/upload_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/client_file_source/upload_layer_wizard.tsx @@ -22,6 +22,7 @@ import { GeojsonFileSource } from './geojson_file_source'; import { VectorLayer } from '../../layers/vector_layer/vector_layer'; export const uploadLayerWizardConfig: LayerWizard = { + categories: [], description: i18n.translate('xpack.maps.source.geojsonFileDescription', { defaultMessage: 'Index GeoJSON data in Elasticsearch', }), diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx index 7eec84ef5bb2e..c53a7a4facb0c 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx @@ -13,8 +13,10 @@ import { EMSFileSource, sourceTitle } from './ems_file_source'; // @ts-ignore import { getIsEmsEnabled } from '../../../kibana_services'; import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const emsBoundariesLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: () => { return getIsEmsEnabled(); }, diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx index 60e67b1ae7053..49d262cbad1a1 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx @@ -13,8 +13,10 @@ import { VectorTileLayer } from '../../layers/vector_tile_layer/vector_tile_laye // @ts-ignore import { TileServiceSelect } from './tile_service_select'; import { getIsEmsEnabled } from '../../../kibana_services'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const emsBaseMapLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: () => { return getIsEmsEnabled(); }, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx index b9d5faa8e18f1..715c16b22dc51 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -23,6 +23,7 @@ import { COUNT_PROP_NAME, COLOR_MAP_TYPE, FIELD_ORIGIN, + LAYER_WIZARD_CATEGORY, RENDER_AS, VECTOR_STYLES, STYLE_TYPE, @@ -30,6 +31,7 @@ import { import { COLOR_GRADIENTS } from '../../styles/color_utils'; export const clustersLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.source.esGridClustersDescription', { defaultMessage: 'Geospatial data grouped in grids with metrics for each gridded cell', }), diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx index 79252c7febf8c..92a0f1006ea43 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/heatmap_layer_wizard.tsx @@ -14,9 +14,10 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re // @ts-ignore import { HeatmapLayer } from '../../layers/heatmap_layer/heatmap_layer'; import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; -import { RENDER_AS } from '../../../../common/constants'; +import { LAYER_WIZARD_CATEGORY, RENDER_AS } from '../../../../common/constants'; export const heatmapLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.source.esGridHeatmapDescription', { defaultMessage: 'Geospatial data grouped in grids to show density', }), diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js index 0d15cff032410..fda73bc0f73a0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/es_pew_pew_source.js @@ -26,13 +26,14 @@ export const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', { export class ESPewPewSource extends AbstractESAggSource { static type = SOURCE_TYPES.ES_PEW_PEW; - static createDescriptor({ indexPatternId, sourceGeoField, destGeoField }) { + static createDescriptor({ indexPatternId, sourceGeoField, destGeoField, metrics }) { return { type: ESPewPewSource.type, id: uuid(), indexPatternId: indexPatternId, sourceGeoField, destGeoField, + metrics: metrics ? metrics : [], }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx index 5169af9bdddf2..ae7414b827c8d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx @@ -14,6 +14,7 @@ import { VectorStyle } from '../../styles/vector/vector_style'; import { FIELD_ORIGIN, COUNT_PROP_NAME, + LAYER_WIZARD_CATEGORY, VECTOR_STYLES, STYLE_TYPE, } from '../../../../common/constants'; @@ -24,6 +25,7 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re import { ColorDynamicOptions, SizeDynamicOptions } from '../../../../common/descriptor_types'; export const point2PointLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.source.pewPewDescription', { defaultMessage: 'Aggregated data paths between the source and destination', }), diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx index 888de2e7297cb..4598b1467229d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_documents_layer_wizard.tsx @@ -13,7 +13,7 @@ import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_re import { ESSearchSource, sourceTitle } from './es_search_source'; import { BlendedVectorLayer } from '../../layers/blended_vector_layer/blended_vector_layer'; import { VectorLayer } from '../../layers/vector_layer/vector_layer'; -import { SCALING_TYPES } from '../../../../common/constants'; +import { LAYER_WIZARD_CATEGORY, SCALING_TYPES } from '../../../../common/constants'; export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: string[]) { const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig); @@ -24,6 +24,7 @@ export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: s } export const esDocumentsLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.source.esSearchDescription', { defaultMessage: 'Vector data from a Kibana index pattern', }), diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx index ca78aaefe404f..c8a1c346646e0 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx @@ -13,8 +13,10 @@ import { VectorLayer } from '../../layers/vector_layer/vector_layer'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; import { getKibanaRegionList } from '../../../meta'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const kibanaRegionMapLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: async () => { const regions = getKibanaRegionList(); return regions.length > 0; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx index 84d2e5e74fa9a..9f63372a78511 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx @@ -13,8 +13,10 @@ import { CreateSourceEditor } from './create_source_editor'; import { KibanaTilemapSource, sourceTitle } from './kibana_tilemap_source'; import { TileLayer } from '../../layers/tile_layer/tile_layer'; import { getKibanaTileMap } from '../../../meta'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const kibanaBasemapLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], checkVisibility: async () => { const tilemap = getKibanaTileMap(); // @ts-ignore diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx index c29302a2058b2..067c7f5a47ca3 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/layer_wizard.tsx @@ -13,8 +13,10 @@ import { import { MVTSingleLayerVectorSource, sourceTitle } from './mvt_single_layer_vector_source'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { TiledVectorLayer } from '../../layers/tiled_vector_layer/tiled_vector_layer'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const mvtVectorSourceWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], description: i18n.translate('xpack.maps.source.mvtVectorSourceWizard', { defaultMessage: 'Vector source wizard', }), diff --git a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_layer_wizard.tsx index 62eeef234f414..b3950baf8dbeb 100644 --- a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_layer_wizard.tsx @@ -12,8 +12,10 @@ import { WMSCreateSourceEditor } from './wms_create_source_editor'; import { sourceTitle, WMSSource } from './wms_source'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { TileLayer } from '../../layers/tile_layer/tile_layer'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const wmsLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], description: i18n.translate('xpack.maps.source.wmsDescription', { defaultMessage: 'Maps from OGC Standard WMS', }), diff --git a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/layer_wizard.tsx index b99b17c1d22d4..48c526855d3a4 100644 --- a/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/xyz_tms_source/layer_wizard.tsx @@ -10,8 +10,10 @@ import { XYZTMSEditor, XYZTMSSourceConfig } from './xyz_tms_editor'; import { XYZTMSSource, sourceTitle } from './xyz_tms_source'; import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; import { TileLayer } from '../../layers/tile_layer/tile_layer'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; export const tmsLayerWizardConfig: LayerWizard = { + categories: [LAYER_WIZARD_CATEGORY.REFERENCE], description: i18n.translate('xpack.maps.source.ems_xyzDescription', { defaultMessage: 'Tile map service configured in interface', }), diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss index 6de2a51590700..bd8070e8c36fd 100644 --- a/x-pack/plugins/maps/public/connected_components/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/_index.scss @@ -1,5 +1,4 @@ @import 'gis_map/gis_map'; -@import 'add_layer_panel/index'; @import 'layer_panel/index'; @import 'widget_overlay/index'; @import 'toolbar_overlay/index'; diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/_index.scss b/x-pack/plugins/maps/public/connected_components/add_layer_panel/_index.scss deleted file mode 100644 index 4e60b8d4b7c4b..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/_index.scss +++ /dev/null @@ -1,12 +0,0 @@ -.mapLayerAddpanel__card { - // EUITODO: Fix horizontal layout so it works with any size icon - .euiCard__content { - // sass-lint:disable-block no-important - padding-top: 0 !important; - } - - .euiCard__top + .euiCard__content { - // sass-lint:disable-block no-important - padding-top: 2px !important; - } -} diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/__snapshots__/layer_wizard_select.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/__snapshots__/layer_wizard_select.test.tsx.snap new file mode 100644 index 0000000000000..ef11f9958d8db --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/__snapshots__/layer_wizard_select.test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LayerWizardSelect Should render layer select after layer wizards are loaded 1`] = ` + + + + + + + Elasticsearch + + + Solutions + + + + + + + + + + } + onClick={[Function]} + title="wizard 2" + /> + + + +`; + +exports[`LayerWizardSelect Should render loading screen before layer wizards are loaded 1`] = ` +
+ + } + layout="horizontal" + title="" + /> +
+`; diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.test.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.test.tsx new file mode 100644 index 0000000000000..e802c5259e5ed --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.test.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. + */ + +jest.mock('../../../classes/layers/layer_wizard_registry', () => ({})); + +import React from 'react'; +import { shallow } from 'enzyme'; +import { LayerWizardSelect } from './layer_wizard_select'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; + +const defaultProps = { + onSelect: () => {}, +}; + +describe('LayerWizardSelect', () => { + beforeAll(() => { + require('../../../classes/layers/layer_wizard_registry').getLayerWizards = async () => { + return [ + { + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], + description: 'mock wizard without icon', + renderWizard: () => { + return
; + }, + title: 'wizard 1', + }, + { + categories: [LAYER_WIZARD_CATEGORY.SOLUTIONS], + description: 'mock wizard with icon', + icon: 'logoObservability', + renderWizard: () => { + return
; + }, + title: 'wizard 2', + }, + ]; + }; + }); + + test('Should render layer select after layer wizards are loaded', async () => { + const component = shallow(); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + test('Should render loading screen before layer wizards are loaded', () => { + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx index 2b1bbfa81c743..f0195bc5dee2f 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx @@ -5,23 +5,63 @@ */ import _ from 'lodash'; -import React, { Component, Fragment } from 'react'; -import { EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; +import React, { Component } from 'react'; +import { + EuiCard, + EuiIcon, + EuiFlexGrid, + EuiFlexItem, + EuiLoadingContent, + EuiFacetGroup, + EuiFacetButton, + EuiSpacer, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { getLayerWizards, LayerWizard } from '../../../classes/layers/layer_wizard_registry'; +import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; interface Props { onSelect: (layerWizard: LayerWizard) => void; } interface State { + activeCategories: LAYER_WIZARD_CATEGORY[]; + hasLoadedWizards: boolean; layerWizards: LayerWizard[]; + selectedCategory: LAYER_WIZARD_CATEGORY | null; +} + +function getCategoryLabel(category: LAYER_WIZARD_CATEGORY): string { + if (category === LAYER_WIZARD_CATEGORY.ELASTICSEARCH) { + return i18n.translate('xpack.maps.layerWizardSelect.elasticsearchCategoryLabel', { + defaultMessage: 'Elasticsearch', + }); + } + + if (category === LAYER_WIZARD_CATEGORY.REFERENCE) { + return i18n.translate('xpack.maps.layerWizardSelect.referenceCategoryLabel', { + defaultMessage: 'Reference', + }); + } + + if (category === LAYER_WIZARD_CATEGORY.SOLUTIONS) { + return i18n.translate('xpack.maps.layerWizardSelect.solutionsCategoryLabel', { + defaultMessage: 'Solutions', + }); + } + + throw new Error(`Unexpected category: ${category}`); } export class LayerWizardSelect extends Component { private _isMounted: boolean = false; state = { + activeCategories: [], + hasLoadedWizards: false, layerWizards: [], + selectedCategory: null, }; componentDidMount() { @@ -35,33 +75,102 @@ export class LayerWizardSelect extends Component { async _loadLayerWizards() { const layerWizards = await getLayerWizards(); + const activeCategories: LAYER_WIZARD_CATEGORY[] = []; + layerWizards.forEach((layerWizard: LayerWizard) => { + layerWizard.categories.forEach((category: LAYER_WIZARD_CATEGORY) => { + if (!activeCategories.includes(category)) { + activeCategories.push(category); + } + }); + }); + if (this._isMounted) { - this.setState({ layerWizards }); + this.setState({ + activeCategories, + layerWizards, + hasLoadedWizards: true, + }); } } - render() { - return this.state.layerWizards.map((layerWizard: LayerWizard) => { - const icon = layerWizard.icon ? : undefined; + _filterByCategory(category: LAYER_WIZARD_CATEGORY | null) { + this.setState({ selectedCategory: category }); + } - const onClick = () => { - this.props.onSelect(layerWizard); - }; + _renderCategoryFacets() { + if (this.state.activeCategories.length === 0) { + return null; + } + const facets = this.state.activeCategories.map((category: LAYER_WIZARD_CATEGORY) => { return ( - - - - + this._filterByCategory(category)} + > + {getCategoryLabel(category)} + ); }); + + return ( + + this._filterByCategory(null)} + > + + + {facets} + + ); + } + + render() { + if (!this.state.hasLoadedWizards) { + return ( +
+ } layout="horizontal" /> +
+ ); + } + + const wizardCards = this.state.layerWizards + .filter((layerWizard: LayerWizard) => { + return this.state.selectedCategory + ? layerWizard.categories.includes(this.state.selectedCategory!) + : true; + }) + .map((layerWizard: LayerWizard) => { + const icon = layerWizard.icon ? : undefined; + + const onClick = () => { + this.props.onSelect(layerWizard); + }; + + return ( + + + + ); + }); + + return ( + <> + {this._renderCategoryFacets()} + + + {wizardCards} + + + ); } } diff --git a/x-pack/plugins/maps/public/connected_components/gis_map/_gis_map.scss b/x-pack/plugins/maps/public/connected_components/gis_map/_gis_map.scss index 85168d970c6de..2180573ef4583 100644 --- a/x-pack/plugins/maps/public/connected_components/gis_map/_gis_map.scss +++ b/x-pack/plugins/maps/public/connected_components/gis_map/_gis_map.scss @@ -9,11 +9,11 @@ overflow: hidden; > * { - width: $euiSizeXXL * 11; + width: $euiSizeXXL * 12; } &-isVisible { - width: $euiSizeXXL * 11; + width: $euiSizeXXL * 12; transition: width $euiAnimSpeedNormal $euiAnimSlightResistance; } } diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index 9428946bb62e0..6a144e84b05e0 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -18,3 +18,4 @@ export const plugin: PluginInitializer = ( export { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; export { ITooltipProperty } from './classes/tooltips/tooltip_property'; +export { MapsPluginStart } from './plugin'; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index 92cefd76aa047..152412376fb09 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -36,6 +36,10 @@ interface LazyLoadedMapModules { initialLayers?: LayerDescriptor[] ) => LayerDescriptor[]; mergeInputWithSavedMap: any; + createSecurityLayerDescriptors: ( + indexPatternId: string, + indexPatternTitle: string + ) => LayerDescriptor[]; } export async function lazyLoadMapModules(): Promise { @@ -56,6 +60,7 @@ export async function lazyLoadMapModules(): Promise { addLayerWithoutDataSync, getInitialLayers, mergeInputWithSavedMap, + createSecurityLayerDescriptors, } = await import('./lazy'); resolve({ @@ -69,6 +74,7 @@ export async function lazyLoadMapModules(): Promise { addLayerWithoutDataSync, getInitialLayers, mergeInputWithSavedMap, + createSecurityLayerDescriptors, }); }); return loadModulesPromise; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index b650678b3105c..0600b8d5073c6 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -16,3 +16,4 @@ export * from '../../actions'; export * from '../../selectors/map_selectors'; export * from '../../angular/get_initial_layers'; export * from '../../embeddable/merge_input_with_saved_map'; +export * from '../../classes/layers/solution_layers/security'; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 319be46870ebc..e0639c9d0f365 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -46,6 +46,8 @@ import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory'; import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public'; import { MapsConfigType, MapsXPackConfig } from '../config'; import { ILicense } from '../../licensing/common/types'; +import { MapsStartApi } from './api'; +import { createSecurityLayerDescriptors } from './api/create_security_layer_descriptors'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -143,7 +145,10 @@ export class MapsPlugin }; } - public start(core: CoreStart, plugins: any) { + public start(core: CoreStart, plugins: any): MapsStartApi { bindStartCoreAndPlugins(core, plugins); + return { + createSecurityLayerDescriptors, + }; } } diff --git a/x-pack/plugins/ml/common/constants/time_format.ts b/x-pack/plugins/ml/common/constants/time_format.ts new file mode 100644 index 0000000000000..109dad2d40ac8 --- /dev/null +++ b/x-pack/plugins/ml/common/constants/time_format.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/index.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/index.ts index 9c299c628426a..deb41fbb832cd 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/index.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/index.ts @@ -10,3 +10,4 @@ export * from './datafeed'; export * from './datafeed_stats'; export * from './combined_job'; export * from './summary_job'; +export * from './model_snapshot'; diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/model_snapshot.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/model_snapshot.ts new file mode 100644 index 0000000000000..367a16965a90b --- /dev/null +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/model_snapshot.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 { JobId } from './job'; +import { ModelSizeStats } from './job_stats'; + +export interface ModelSnapshot { + job_id: JobId; + min_version: string; + timestamp: number; + description: string; + snapshot_id: string; + snapshot_doc_count: number; + model_size_stats: ModelSizeStats; + latest_record_time_stamp: number; + latest_result_time_stamp: number; + retain: boolean; +} diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 4b6ff8c64822b..b871d857f7fde 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -35,6 +35,8 @@ const App: FC = ({ coreStart, deps }) => { }; const services = { appName: 'ML', + kibanaVersion: deps.kibanaVersion, + share: deps.share, data: deps.data, security: deps.security, licenseManagement: deps.licenseManagement, diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index 52d266cde1a2c..a091da6c359d1 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -43,6 +43,7 @@ import { getLatestDataOrBucketTimestamp, isTimeSeriesViewJob, } from '../../../../../common/util/job_utils'; +import { TIME_FORMAT } from '../../../../../common/constants/time_format'; import { annotation$, @@ -50,8 +51,6 @@ import { annotationsRefreshed, } from '../../../services/annotations_service'; -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; - /** * Table component for rendering the lists of annotations for an ML job. */ diff --git a/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts b/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts index 93a1ec40f1d5e..8c92f47e5aa07 100644 --- a/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts +++ b/x-pack/plugins/ml/public/application/components/color_range_legend/index.ts @@ -11,4 +11,5 @@ export { useColorRange, COLOR_RANGE, COLOR_RANGE_SCALE, + useCurrentEuiTheme, } from './use_color_range'; diff --git a/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts b/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts index 1d5f5cf3a0309..f674372da6785 100644 --- a/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts +++ b/x-pack/plugins/ml/public/application/components/color_range_legend/use_color_range.ts @@ -5,7 +5,7 @@ */ import d3 from 'd3'; - +import { useMemo } from 'react'; import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; @@ -150,7 +150,7 @@ export const useColorRange = ( colorRangeScale = COLOR_RANGE_SCALE.LINEAR, featureCount = 1 ) => { - const euiTheme = useUiSettings().get('theme:darkMode') ? euiThemeDark : euiThemeLight; + const { euiTheme } = useCurrentEuiTheme(); const colorRanges: Record = { [COLOR_RANGE.BLUE]: [ @@ -186,3 +186,11 @@ export const useColorRange = ( return scaleTypes[colorRangeScale]; }; + +export function useCurrentEuiTheme() { + const uiSettings = useUiSettings(); + return useMemo( + () => ({ euiTheme: uiSettings.get('theme:darkMode') ? euiThemeDark : euiThemeLight }), + [uiSettings] + ); +} diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index fd2b7902833a6..798ceae0f0732 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -13,10 +13,9 @@ import { i18n } from '@kbn/i18n'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { JobMessage } from '../../../../common/types/audit_message'; +import { TIME_FORMAT } from '../../../../common/constants/time_format'; import { JobIcon } from '../job_message_icon'; -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; - interface JobMessagesProps { messages: JobMessage[]; loading: boolean; diff --git a/x-pack/plugins/ml/public/application/components/loading_indicator/index.js b/x-pack/plugins/ml/public/application/components/loading_indicator/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/components/loading_indicator/index.js rename to x-pack/plugins/ml/public/application/components/loading_indicator/index.ts diff --git a/x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.js b/x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.tsx similarity index 70% rename from x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.js rename to x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.tsx index 20f4fb86b5372..364b23a27eaf7 100644 --- a/x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.js +++ b/x-pack/plugins/ml/public/application/components/loading_indicator/loading_indicator.tsx @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiLoadingChart, EuiSpacer } from '@elastic/eui'; -export function LoadingIndicator({ height, label }) { +export const LoadingIndicator: FC<{ height?: number; label?: string }> = ({ height, label }) => { height = height ? +height : 100; return (
-
{label}
+
{label}
)}
); -} -LoadingIndicator.propTypes = { - height: PropTypes.number, - label: PropTypes.string, }; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx new file mode 100644 index 0000000000000..8716f8c85f208 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.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 } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; + +import { COMBINED_JOB_STATE } from '../model_snapshots_table'; + +interface Props { + combinedJobState: COMBINED_JOB_STATE; + hideCloseJobModalVisible(): void; + forceCloseJob(): void; +} +export const CloseJobConfirm: FC = ({ + combinedJobState, + hideCloseJobModalVisible, + forceCloseJob, +}) => { + return ( + + +

+ {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_RUNNING && ( + + )} + {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_STOPPED && ( + + )} +
+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/index.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/index.ts new file mode 100644 index 0000000000000..195d14160b5e6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/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 { CloseJobConfirm } from './close_job_confirm'; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx new file mode 100644 index 0000000000000..1e99fd12a9fa6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useCallback, useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiTextArea, + EuiFormRow, + EuiSwitch, + EuiConfirmModal, + EuiOverlayMask, + EuiCallOut, +} from '@elastic/eui'; + +import { + ModelSnapshot, + CombinedJobWithStats, +} from '../../../../../common/types/anomaly_detection_jobs'; +import { ml } from '../../../services/ml_api_service'; +import { useNotifications } from '../../../contexts/kibana'; + +interface Props { + snapshot: ModelSnapshot; + job: CombinedJobWithStats; + closeFlyout(reload: boolean): void; +} + +export const EditModelSnapshotFlyout: FC = ({ snapshot, job, closeFlyout }) => { + const { toasts } = useNotifications(); + const [description, setDescription] = useState(snapshot.description); + const [retain, setRetain] = useState(snapshot.retain); + const [deleteModalVisible, setDeleteModalVisible] = useState(false); + const [isCurrentSnapshot, setIsCurrentSnapshot] = useState( + snapshot.snapshot_id === job.model_snapshot_id + ); + + useEffect(() => { + setIsCurrentSnapshot(snapshot.snapshot_id === job.model_snapshot_id); + }, [snapshot]); + + const updateSnapshot = useCallback(async () => { + try { + await ml.updateModelSnapshot(snapshot.job_id, snapshot.snapshot_id, { + description, + retain, + }); + closeWithReload(); + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.editModelSnapshotFlyout.saveErrorTitle', { + defaultMessage: 'Model snapshot update failed', + }), + }); + } + }, [retain, description, snapshot]); + + const deleteSnapshot = useCallback(async () => { + try { + await ml.deleteModelSnapshot(snapshot.job_id, snapshot.snapshot_id); + hideDeleteModal(); + closeWithReload(); + } catch (error) { + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.editModelSnapshotFlyout.deleteErrorTitle', { + defaultMessage: 'Model snapshot deletion failed', + }), + }); + } + }, [snapshot]); + + function closeWithReload() { + closeFlyout(true); + } + function closeWithoutReload() { + closeFlyout(false); + } + function showDeleteModal() { + setDeleteModalVisible(true); + } + function hideDeleteModal() { + setDeleteModalVisible(false); + } + + return ( + <> + + + + +
+ +
+
+ + {isCurrentSnapshot && ( + <> + + + + + + )} + + + + + setDescription(e.target.value)} + /> + + + + setRetain(e.target.checked)} + /> + +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + {deleteModalVisible && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/index.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/index.ts new file mode 100644 index 0000000000000..fcb534620e438 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/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 { EditModelSnapshotFlyout } from './edit_model_snapshot_flyout'; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/index.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/index.ts new file mode 100644 index 0000000000000..e16d69ea3eb83 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/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 { ModelSnapshotTable } from './model_snapshots_table'; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx new file mode 100644 index 0000000000000..64fdd97903b60 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/model_snapshots_table.tsx @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiLoadingSpinner, + EuiBasicTableColumn, + formatDate, +} from '@elastic/eui'; + +import { checkPermission } from '../../capabilities/check_capabilities'; +import { EditModelSnapshotFlyout } from './edit_model_snapshot_flyout'; +import { RevertModelSnapshotFlyout } from './revert_model_snapshot_flyout'; +import { ml } from '../../services/ml_api_service'; +import { JOB_STATE, DATAFEED_STATE } from '../../../../common/constants/states'; +import { TIME_FORMAT } from '../../../../common/constants/time_format'; +import { CloseJobConfirm } from './close_job_confirm'; +import { + ModelSnapshot, + CombinedJobWithStats, +} from '../../../../common/types/anomaly_detection_jobs'; + +interface Props { + job: CombinedJobWithStats; + refreshJobList: () => void; +} + +export enum COMBINED_JOB_STATE { + OPEN_AND_RUNNING, + OPEN_AND_STOPPED, + CLOSED, + UNKNOWN, +} + +export const ModelSnapshotTable: FC = ({ job, refreshJobList }) => { + const canCreateJob = checkPermission('canCreateJob'); + const canStartStopDatafeed = checkPermission('canStartStopDatafeed'); + + const [snapshots, setSnapshots] = useState([]); + const [snapshotsLoaded, setSnapshotsLoaded] = useState(false); + const [editSnapshot, setEditSnapshot] = useState(null); + const [revertSnapshot, setRevertSnapshot] = useState(null); + const [closeJobModalVisible, setCloseJobModalVisible] = useState(null); + const [combinedJobState, setCombinedJobState] = useState(null); + + useEffect(() => { + loadModelSnapshots(); + }, []); + + const loadModelSnapshots = useCallback(async () => { + const { model_snapshots: ms } = await ml.getModelSnapshots(job.job_id); + setSnapshots(ms); + setSnapshotsLoaded(true); + }, [job]); + + const checkJobIsClosed = useCallback( + async (snapshot: ModelSnapshot) => { + const state = await getCombinedJobState(job.job_id); + if (state === COMBINED_JOB_STATE.UNKNOWN) { + // this will only happen if the job has been deleted by another user + // between the time the row has been expended and now + // eslint-disable-next-line no-console + console.error(`Error retrieving state for job ${job.job_id}`); + return; + } + + setCombinedJobState(state); + + if (state === COMBINED_JOB_STATE.CLOSED) { + // show flyout + setRevertSnapshot(snapshot); + } else { + // show close job modal + setCloseJobModalVisible(snapshot); + } + }, + [job] + ); + + function hideCloseJobModalVisible() { + setCombinedJobState(null); + setCloseJobModalVisible(null); + } + + const forceCloseJob = useCallback(async () => { + await ml.jobs.forceStopAndCloseJob(job.job_id); + if (closeJobModalVisible !== null) { + const state = await getCombinedJobState(job.job_id); + if (state === COMBINED_JOB_STATE.CLOSED) { + setRevertSnapshot(closeJobModalVisible); + } + } + hideCloseJobModalVisible(); + }, [job, closeJobModalVisible]); + + const closeEditFlyout = useCallback((reload: boolean) => { + setEditSnapshot(null); + if (reload) { + loadModelSnapshots(); + } + }, []); + + const closeRevertFlyout = useCallback((reload: boolean) => { + setRevertSnapshot(null); + if (reload) { + loadModelSnapshots(); + // wait half a second before refreshing the jobs list + setTimeout(refreshJobList, 500); + } + }, []); + + const columns: Array> = [ + { + field: 'snapshot_id', + name: i18n.translate('xpack.ml.modelSnapshotTable.id', { + defaultMessage: 'ID', + }), + sortable: true, + }, + { + field: 'description', + name: i18n.translate('xpack.ml.modelSnapshotTable.description', { + defaultMessage: 'Description', + }), + sortable: true, + }, + { + field: 'timestamp', + name: i18n.translate('xpack.ml.modelSnapshotTable.time', { + defaultMessage: 'Date created', + }), + dataType: 'date', + render: renderDate, + sortable: true, + }, + { + field: 'latest_record_time_stamp', + name: i18n.translate('xpack.ml.modelSnapshotTable.latestTimestamp', { + defaultMessage: 'Latest timestamp', + }), + dataType: 'date', + render: renderDate, + sortable: true, + }, + { + field: 'retain', + name: i18n.translate('xpack.ml.modelSnapshotTable.retain', { + defaultMessage: 'Retain', + }), + width: '100px', + sortable: true, + }, + { + field: '', + width: '100px', + name: i18n.translate('xpack.ml.modelSnapshotTable.actions', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: i18n.translate('xpack.ml.modelSnapshotTable.actions.revert.name', { + defaultMessage: 'Revert', + }), + description: i18n.translate('xpack.ml.modelSnapshotTable.actions.revert.description', { + defaultMessage: 'Revert to this snapshot', + }), + enabled: () => canCreateJob && canStartStopDatafeed, + type: 'icon', + icon: 'crosshairs', + onClick: checkJobIsClosed, + }, + { + name: i18n.translate('xpack.ml.modelSnapshotTable.actions.edit.name', { + defaultMessage: 'Edit', + }), + description: i18n.translate('xpack.ml.modelSnapshotTable.actions.edit.description', { + defaultMessage: 'Edit this snapshot', + }), + enabled: () => canCreateJob, + type: 'icon', + icon: 'pencil', + onClick: setEditSnapshot, + }, + ], + }, + ]; + + if (snapshotsLoaded === false) { + return ( + <> + + + + + + + ); + } + + return ( + <> + + {editSnapshot !== null && ( + + )} + + {revertSnapshot !== null && ( + + )} + + {closeJobModalVisible !== null && combinedJobState !== null && ( + + )} + + ); +}; + +function renderDate(date: number) { + return formatDate(date, TIME_FORMAT); +} + +async function getCombinedJobState(jobId: string) { + const jobs = await ml.jobs.jobs([jobId]); + + if (jobs.length !== 1) { + return COMBINED_JOB_STATE.UNKNOWN; + } + + if (jobs[0].state !== JOB_STATE.CLOSED) { + if (jobs[0].datafeed_config.state !== DATAFEED_STATE.STOPPED) { + return COMBINED_JOB_STATE.OPEN_AND_RUNNING; + } + return COMBINED_JOB_STATE.OPEN_AND_STOPPED; + } + return COMBINED_JOB_STATE.CLOSED; +} diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts new file mode 100644 index 0000000000000..2da1e914b8139 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/chart_loader.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MlResultsService } from '../../../services/results_service'; +import { CombinedJobWithStats } from '../../../../../common/types/anomaly_detection_jobs'; +import { getSeverityType } from '../../../../../common/util/anomaly_utils'; +import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loader'; +import { LineChartPoint } from '../../../jobs/new_job/common/chart_loader/chart_loader'; + +export function chartLoaderProvider(mlResultsService: MlResultsService) { + async function loadEventRateForJob( + job: CombinedJobWithStats, + bucketSpanMs: number, + bars: number + ): Promise { + const intervalMs = Math.max( + Math.floor( + (job.data_counts.latest_record_timestamp - job.data_counts.earliest_record_timestamp) / bars + ), + bucketSpanMs + ); + const resp = await mlResultsService.getEventRateData( + job.datafeed_config.indices.join(), + job.datafeed_config.query, + job.data_description.time_field, + job.data_counts.earliest_record_timestamp, + job.data_counts.latest_record_timestamp, + intervalMs + ); + if (resp.error !== undefined) { + throw resp.error; + } + + const events = Object.entries(resp.results).map(([time, value]) => ({ + time: +time, + value: value as number, + })); + + if (events.length) { + // add one extra bucket with a value of 0 + // so that an extra blank bar gets drawn at the end of the chart + // this solves an issue with elastic charts where the rect annotation + // never covers the last bar. + events.push({ time: events[events.length - 1].time + intervalMs, value: 0 }); + } + + return events; + } + + async function loadAnomalyDataForJob( + job: CombinedJobWithStats, + bucketSpanMs: number, + bars: number + ) { + const intervalMs = Math.max( + Math.floor( + (job.data_counts.latest_record_timestamp - job.data_counts.earliest_record_timestamp) / bars + ), + bucketSpanMs + ); + + const resp = await mlResultsService.getScoresByBucket( + [job.job_id], + job.data_counts.earliest_record_timestamp, + job.data_counts.latest_record_timestamp, + intervalMs, + 1 + ); + + const results = resp.results[job.job_id]; + if (results === undefined) { + return []; + } + + const anomalies: Record = {}; + anomalies[0] = Object.entries(results).map( + ([time, value]) => + ({ time: +time, value, severity: getSeverityType(value as number) } as Anomaly) + ); + return anomalies; + } + + return { loadEventRateForJob, loadAnomalyDataForJob }; +} diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/create_calendar.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/create_calendar.tsx new file mode 100644 index 0000000000000..937c394e35bc1 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/create_calendar.tsx @@ -0,0 +1,310 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, Fragment, useCallback, memo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import moment from 'moment'; +import { XYBrushArea } from '@elastic/charts'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiFormRow, + EuiFieldText, + EuiDatePicker, + EuiButtonIcon, + EuiPanel, +} from '@elastic/eui'; + +import { EventRateChart } from '../../../jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart'; +import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loader'; +import { useCurrentEuiTheme } from '../../../components/color_range_legend'; +import { LineChartPoint } from '../../../jobs/new_job/common/chart_loader/chart_loader'; + +export interface CalendarEvent { + start: moment.Moment | null; + end: moment.Moment | null; + description: string; +} + +interface Props { + calendarEvents: CalendarEvent[]; + setCalendarEvents: (calendars: CalendarEvent[]) => void; + minSelectableTimeStamp: number; + maxSelectableTimeStamp: number; + eventRateData: LineChartPoint[]; + anomalies: Anomaly[]; + chartReady: boolean; +} + +export const CreateCalendar: FC = ({ + calendarEvents, + setCalendarEvents, + minSelectableTimeStamp, + maxSelectableTimeStamp, + eventRateData, + anomalies, + chartReady, +}) => { + const maxSelectableTimeMoment = moment(maxSelectableTimeStamp); + const minSelectableTimeMoment = moment(minSelectableTimeStamp); + + const { euiTheme } = useCurrentEuiTheme(); + + const onBrushEnd = useCallback( + ({ x }: XYBrushArea) => { + if (x && x.length === 2) { + const end = x[1] < minSelectableTimeStamp ? null : x[1]; + if (end !== null) { + const start = x[0] < minSelectableTimeStamp ? minSelectableTimeStamp : x[0]; + + setCalendarEvents([ + ...calendarEvents, + { + start: moment(start), + end: moment(end), + description: createDefaultEventDescription(calendarEvents.length + 1), + }, + ]); + } + } + }, + [calendarEvents] + ); + + const setStartDate = useCallback( + (start: moment.Moment | null, index: number) => { + const event = calendarEvents[index]; + if (event === undefined) { + setCalendarEvents([ + ...calendarEvents, + { start, end: null, description: createDefaultEventDescription(index) }, + ]); + } else { + event.start = start; + setCalendarEvents([...calendarEvents]); + } + }, + [calendarEvents] + ); + + const setEndDate = useCallback( + (end: moment.Moment | null, index: number) => { + const event = calendarEvents[index]; + if (event === undefined) { + setCalendarEvents([ + ...calendarEvents, + { start: null, end, description: createDefaultEventDescription(index) }, + ]); + } else { + event.end = end; + setCalendarEvents([...calendarEvents]); + } + }, + [calendarEvents] + ); + + const setDescription = useCallback( + (description: string, index: number) => { + const event = calendarEvents[index]; + if (event !== undefined) { + event.description = description; + setCalendarEvents([...calendarEvents]); + } + }, + [calendarEvents] + ); + + const removeCalendarEvent = useCallback( + (index: number) => { + if (calendarEvents[index] !== undefined) { + const ce = [...calendarEvents]; + ce.splice(index, 1); + setCalendarEvents(ce); + } + }, + [calendarEvents] + ); + + return ( + <> + +
+ +
+ + ({ + start: c.start!.valueOf(), + end: c.end!.valueOf(), + }))} + onBrushEnd={onBrushEnd} + overlayColor={euiTheme.euiColorPrimary} + /> + + + {calendarEvents.map((c, i) => ( + + + + + + + + setStartDate(d, i)} + /> + + + + + setEndDate(d, i)} + /> + + + + + + + + setDescription(e.target.value, i)} + /> + + + + + + + removeCalendarEvent(i)} + iconType="trash" + aria-label={i18n.translate( + 'xpack.ml.revertModelSnapshotFlyout.createCalendar.deleteLabel', + { + defaultMessage: 'Delete event', + } + )} + /> + + + + + + ))} + + ); +}; + +interface ChartProps { + eventRateData: LineChartPoint[]; + anomalies: Anomaly[]; + loading: boolean; + onBrushEnd(area: XYBrushArea): void; + overlayRanges: Array<{ start: number; end: number }>; + overlayColor: string; +} + +const Chart: FC = memo( + ({ eventRateData, anomalies, loading, onBrushEnd, overlayRanges, overlayColor }) => ( + ({ + start: c.start, + end: c.end, + color: overlayColor, + showMarker: false, + }))} + onBrushEnd={onBrushEnd} + /> + ), + (prev: ChartProps, next: ChartProps) => { + // only redraw if the calendar ranges have changes + return ( + prev.overlayRanges.length === next.overlayRanges.length && + JSON.stringify(prev.overlayRanges) === JSON.stringify(next.overlayRanges) + ); + } +); + +function filterIncompleteEvents(event: CalendarEvent): event is CalendarEvent { + return event.start !== null && event.end !== null; +} + +function createDefaultEventDescription(index: number) { + return i18n.translate( + 'xpack.ml.revertModelSnapshotFlyout.createCalendar.defaultEventDescription', + { + defaultMessage: 'Auto created event {index}', + values: { index }, + } + ); +} diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/index.ts b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/index.ts new file mode 100644 index 0000000000000..33adc65a9e327 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/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 { RevertModelSnapshotFlyout } from './revert_model_snapshot_flyout'; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx new file mode 100644 index 0000000000000..ad5915b39d521 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx @@ -0,0 +1,409 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useState, useCallback, useMemo, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import {} from 'lodash'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiFormRow, + EuiSwitch, + EuiConfirmModal, + EuiOverlayMask, + EuiCallOut, + EuiHorizontalRule, + EuiSuperSelect, + EuiText, + formatDate, +} from '@elastic/eui'; + +import { + ModelSnapshot, + CombinedJobWithStats, +} from '../../../../../common/types/anomaly_detection_jobs'; +import { ml } from '../../../services/ml_api_service'; +import { useNotifications } from '../../../contexts/kibana'; +import { chartLoaderProvider } from './chart_loader'; +import { mlResultsService } from '../../../services/results_service'; +import { LineChartPoint } from '../../../jobs/new_job/common/chart_loader'; +import { EventRateChart } from '../../../jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart'; +import { Anomaly } from '../../../jobs/new_job/common/results_loader/results_loader'; +import { parseInterval } from '../../../../../common/util/parse_interval'; +import { TIME_FORMAT } from '../../../../../common/constants/time_format'; +import { CreateCalendar, CalendarEvent } from './create_calendar'; + +interface Props { + snapshot: ModelSnapshot; + snapshots: ModelSnapshot[]; + job: CombinedJobWithStats; + closeFlyout(reload: boolean): void; +} + +export const RevertModelSnapshotFlyout: FC = ({ snapshot, snapshots, job, closeFlyout }) => { + const { toasts } = useNotifications(); + const { loadAnomalyDataForJob, loadEventRateForJob } = useMemo( + () => chartLoaderProvider(mlResultsService), + [] + ); + const [currentSnapshot, setCurrentSnapshot] = useState(snapshot); + const [revertModalVisible, setRevertModalVisible] = useState(false); + const [replay, setReplay] = useState(false); + const [runInRealTime, setRunInRealTime] = useState(false); + const [createCalendar, setCreateCalendar] = useState(false); + const [calendarEvents, setCalendarEvents] = useState([]); + const [calendarEventsValid, setCalendarEventsValid] = useState(true); + + const [eventRateData, setEventRateData] = useState([]); + const [anomalies, setAnomalies] = useState([]); + const [chartReady, setChartReady] = useState(false); + const [applying, setApplying] = useState(false); + + useEffect(() => { + createChartData(); + }, [currentSnapshot]); + + useEffect(() => { + const invalid = calendarEvents.some( + (c) => c.description === '' || c.end === null || c.start === null + ); + setCalendarEventsValid(invalid === false); + + // a bug in elastic charts selection can + // cause duplicate selected areas to be added + // dedupe the calendars based on start and end times + const calMap = new Map( + calendarEvents.map((c) => [`${c.start?.valueOf()}${c.end?.valueOf()}`, c]) + ); + const dedupedCalendarEvents = [...calMap.values()]; + + if (dedupedCalendarEvents.length < calendarEvents.length) { + // deduped list is shorter, we must have removed something. + setCalendarEvents(dedupedCalendarEvents); + } + }, [calendarEvents]); + + const createChartData = useCallback(async () => { + const bucketSpanMs = parseInterval(job.analysis_config.bucket_span)!.asMilliseconds(); + const eventRate = await loadEventRateForJob(job, bucketSpanMs, 100); + const anomalyData = await loadAnomalyDataForJob(job, bucketSpanMs, 100); + setEventRateData(eventRate); + if (anomalyData[0] !== undefined) { + setAnomalies(anomalyData[0]); + } + setChartReady(true); + }, [job]); + + function closeWithReload() { + closeFlyout(true); + } + function closeWithoutReload() { + closeFlyout(false); + } + + function showRevertModal() { + setRevertModalVisible(true); + } + function hideRevertModal() { + setRevertModalVisible(false); + } + + async function applyRevert() { + setApplying(true); + const end = + replay && runInRealTime === false ? job.data_counts.latest_record_timestamp : undefined; + try { + const events = + replay && createCalendar + ? calendarEvents.filter(filterIncompleteEvents).map((c) => ({ + start: c.start!.valueOf(), + end: c.end!.valueOf(), + description: c.description, + })) + : undefined; + + await ml.jobs.revertModelSnapshot( + job.job_id, + currentSnapshot.snapshot_id, + replay, + end, + events + ); + hideRevertModal(); + closeWithReload(); + } catch (error) { + setApplying(false); + toasts.addError(new Error(error.body.message), { + title: i18n.translate('xpack.ml.revertModelSnapshotFlyout.revertErrorTitle', { + defaultMessage: 'Model snapshot revert failed', + }), + }); + } + } + + function onSnapshotChange(ssId: string) { + const ss = snapshots.find((s) => s.snapshot_id === ssId); + if (ss !== undefined) { + setCurrentSnapshot(ss); + } + } + + return ( + <> + + + +
+ +
+
+ + +

{currentSnapshot.description}

+
+
+ + {false && ( // disabled for now + <> + + + + ({ + value: s.snapshot_id, + inputDisplay: s.snapshot_id, + dropdownDisplay: ( + <> + {s.snapshot_id} + +

{s.description}

+
+ + ), + })) + .reverse()} + valueOfSelected={currentSnapshot.snapshot_id} + onChange={onSnapshotChange} + itemLayoutAlign="top" + hasDividers + /> +
+ + + + )} + + + + + + + + + + + + + + setReplay(e.target.checked)} + /> + + + {replay && ( + <> + + setRunInRealTime(e.target.checked)} + /> + + + + setCreateCalendar(e.target.checked)} + /> + + + {createCalendar && ( + + )} + + )} +
+ + + + + + + + + + + + + + + + +
+ + {revertModalVisible && ( + + + + )} + + ); +}; + +function filterIncompleteEvents(event: CalendarEvent): event is CalendarEvent { + return event.start !== null && event.end !== null; +} 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 c65d872212ad6..2a156b5716ad4 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 @@ -12,13 +12,15 @@ import { } from '../../../../../../../src/plugins/kibana_react/public'; import { SecurityPluginSetup } from '../../../../../security/public'; import { LicenseManagementUIPluginSetup } from '../../../../../license_management/public'; +import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; interface StartPlugins { data: DataPublicPluginStart; security?: SecurityPluginSetup; licenseManagement?: LicenseManagementUIPluginSetup; + share: SharePluginStart; } -export type StartServices = CoreStart & StartPlugins; +export type StartServices = CoreStart & StartPlugins & { kibanaVersion: string }; // eslint-disable-next-line react-hooks/rules-of-hooks export const useMlKibana = () => useKibana(); export type MlKibanaReactContextValue = KibanaReactContextValue; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts index f8abd48ce8562..07d5a153664b7 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts @@ -14,6 +14,7 @@ export interface MlContextValue { currentSavedSearch: SavedSearchSavedObject | null; indexPatterns: IndexPatternsContract; kibanaConfig: any; // IUiSettingsClient; + kibanaVersion: string; } export type SavedSearchQuery = object; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx index e437d27372a3e..b6b335afa53f5 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx @@ -7,29 +7,38 @@ import React, { FC, Fragment } from 'react'; import { EuiCard, EuiHorizontalRule, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../../../../../contexts/kibana'; -function redirectToAnalyticsManagementPage() { - window.location.href = '#/data_frame_analytics?'; -} +export const BackToListPanel: FC = () => { + const { + services: { + application: { navigateToUrl }, + }, + } = useMlKibana(); -export const BackToListPanel: FC = () => ( - - - } - title={i18n.translate('xpack.ml.dataframe.analytics.create.analyticsListCardTitle', { - defaultMessage: 'Data Frame Analytics', - })} - description={i18n.translate( - 'xpack.ml.dataframe.analytics.create.analyticsListCardDescription', - { - defaultMessage: 'Return to the analytics management page.', - } - )} - onClick={redirectToAnalyticsManagementPage} - data-test-subj="analyticsWizardCardManagement" - /> - -); + const redirectToAnalyticsManagementPage = async () => { + await navigateToUrl('#/data_frame_analytics?'); + }; + + return ( + + + } + title={i18n.translate('xpack.ml.dataframe.analytics.create.analyticsListCardTitle', { + defaultMessage: 'Data Frame Analytics', + })} + description={i18n.translate( + 'xpack.ml.dataframe.analytics.create.analyticsListCardDescription', + { + defaultMessage: 'Return to the analytics management page.', + } + )} + onClick={redirectToAnalyticsManagementPage} + data-test-subj="analyticsWizardCardManagement" + /> + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index 24e5785c6e808..105eb9f73804d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -74,8 +74,9 @@ export const ExplorationResultsTable: FC = React.memo( if (jobConfig === undefined || classificationData === undefined) { return null; } + // if it's a searchBar syntax error leave the table visible so they can try again - if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) { + if (status === INDEX_STATUS.ERROR && !errorMessage.includes('failed to create query')) { return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 58f8528236bb9..917ab1b0ed1dd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -56,7 +56,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = const { columns, errorMessage, status, tableItems } = outlierData; // if it's a searchBar syntax error leave the table visible so they can try again - if (status === INDEX_STATUS.ERROR && !errorMessage.includes('parsing_exception')) { + if (status === INDEX_STATUS.ERROR && !errorMessage.includes('failed to create query')) { return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx index e8b1cd1a5696a..df7dce7217fd4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -360,7 +360,14 @@ export const CloneAction: FC = ({ createAnalyticsForm, item }) defaultMessage: 'Clone job', }); - const { notifications, savedObjects } = useMlKibana().services; + const { + services: { + application: { navigateToUrl }, + notifications: { toasts }, + savedObjects, + }, + } = useMlKibana(); + const savedObjectsClient = savedObjects.client; const onClick = async () => { @@ -385,7 +392,6 @@ export const CloneAction: FC = ({ createAnalyticsForm, item }) sourceIndexId = ip.id; } } catch (e) { - const { toasts } = notifications; const error = extractErrorMessage(e); toasts.addDanger( @@ -401,9 +407,11 @@ export const CloneAction: FC = ({ createAnalyticsForm, item }) } if (sourceIndexId) { - window.location.href = `ml#/data_frame_analytics/new_job?index=${encodeURIComponent( - sourceIndexId - )}&jobId=${item.config.id}`; + await navigateToUrl( + `ml#/data_frame_analytics/new_job?index=${encodeURIComponent(sourceIndexId)}&jobId=${ + item.config.id + }` + ); } }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index d20afe93d2b9d..b03a58a02309d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -26,12 +26,20 @@ interface Props { } export const SourceSelection: FC = ({ onClose }) => { - const { uiSettings, savedObjects } = useMlKibana().services; + const { + services: { + application: { navigateToUrl }, + savedObjects, + uiSettings, + }, + } = useMlKibana(); - const onSearchSelected = (id: string, type: string) => { - window.location.href = `ml#/data_frame_analytics/new_job?${ - type === 'index-pattern' ? 'index' : 'savedSearchId' - }=${encodeURIComponent(id)}`; + const onSearchSelected = async (id: string, type: string) => { + await navigateToUrl( + `ml#/data_frame_analytics/new_job?${ + type === 'index-pattern' ? 'index' : 'savedSearchId' + }=${encodeURIComponent(id)}` + ); }; return ( diff --git a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx new file mode 100644 index 0000000000000..cb11a33ccfd76 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx @@ -0,0 +1,321 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useCallback, useMemo, useState, useEffect } from 'react'; +import { debounce } from 'lodash'; +import { + EuiFormRow, + EuiCheckboxGroup, + EuiInMemoryTableProps, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiSpacer, + EuiButtonEmpty, + EuiButton, + EuiModalFooter, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiModalBody } from '@elastic/eui'; +import { EuiInMemoryTable } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../contexts/kibana'; +import { SavedObjectDashboard } from '../../../../../../src/plugins/dashboard/public'; +import { + ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, + getDefaultPanelTitle, +} from '../../embeddables/anomaly_swimlane/anomaly_swimlane_embeddable'; +import { useDashboardService } from '../services/dashboard_service'; +import { SWIMLANE_TYPE, SwimlaneType } from './explorer_constants'; +import { JobId } from '../../../common/types/anomaly_detection_jobs'; + +export interface DashboardItem { + id: string; + title: string; + description: string | undefined; + attributes: SavedObjectDashboard; +} + +export type EuiTableProps = EuiInMemoryTableProps; + +function getDefaultEmbeddablepaPanelConfig(jobIds: JobId[]) { + return { + type: ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, + title: getDefaultPanelTitle(jobIds), + }; +} + +interface AddToDashboardControlProps { + jobIds: JobId[]; + viewBy: string; + limit: number; + onClose: (callback?: () => Promise) => void; +} + +/** + * Component for attaching anomaly swimlane embeddable to dashboards. + */ +export const AddToDashboardControl: FC = ({ + onClose, + jobIds, + viewBy, + limit, +}) => { + const { + notifications: { toasts }, + services: { + application: { navigateToUrl }, + }, + } = useMlKibana(); + + useEffect(() => { + fetchDashboards(); + + return () => { + fetchDashboards.cancel(); + }; + }, []); + + const dashboardService = useDashboardService(); + + const [isLoading, setIsLoading] = useState(false); + const [selectedSwimlanes, setSelectedSwimlanes] = useState<{ [key in SwimlaneType]: boolean }>({ + [SWIMLANE_TYPE.OVERALL]: true, + [SWIMLANE_TYPE.VIEW_BY]: false, + }); + const [dashboardItems, setDashboardItems] = useState([]); + const [selectedItems, setSelectedItems] = useState([]); + + const fetchDashboards = useCallback( + debounce(async (query?: string) => { + try { + const response = await dashboardService.fetchDashboards(query); + const items: DashboardItem[] = response.savedObjects.map((savedObject) => { + return { + id: savedObject.id, + title: savedObject.attributes.title, + description: savedObject.attributes.description, + attributes: savedObject.attributes, + }; + }); + setDashboardItems(items); + } catch (e) { + toasts.danger({ + body: e, + }); + } + setIsLoading(false); + }, 500), + [] + ); + + const search: EuiTableProps['search'] = useMemo(() => { + return { + onChange: ({ queryText }) => { + setIsLoading(true); + fetchDashboards(queryText); + }, + box: { + incremental: true, + 'data-test-subj': 'mlDashboardsSearchBox', + }, + }; + }, []); + + const addSwimlaneToDashboardCallback = useCallback(async () => { + const swimlanes = Object.entries(selectedSwimlanes) + .filter(([, isSelected]) => isSelected) + .map(([swimlaneType]) => swimlaneType); + + for (const selectedDashboard of selectedItems) { + const panelsData = swimlanes.map((swimlaneType) => { + const config = getDefaultEmbeddablepaPanelConfig(jobIds); + if (swimlaneType === SWIMLANE_TYPE.VIEW_BY) { + return { + ...config, + embeddableConfig: { + jobIds, + swimlaneType, + viewBy, + limit, + }, + }; + } + return { + ...config, + embeddableConfig: { + jobIds, + swimlaneType, + }, + }; + }); + + try { + await dashboardService.attachPanels( + selectedDashboard.id, + selectedDashboard.attributes, + panelsData + ); + toasts.success({ + title: ( + + ), + toastLifeTimeMs: 3000, + }); + } catch (e) { + toasts.danger({ + body: e, + }); + } + } + }, [selectedSwimlanes, selectedItems]); + + const columns: EuiTableProps['columns'] = [ + { + field: 'title', + name: i18n.translate('xpack.ml.explorer.dashboardsTable.titleColumnHeader', { + defaultMessage: 'Title', + }), + sortable: true, + truncateText: true, + }, + { + field: 'description', + name: i18n.translate('xpack.ml.explorer.dashboardsTable.descriptionColumnHeader', { + defaultMessage: 'Description', + }), + truncateText: true, + }, + ]; + + const swimlaneTypeOptions = [ + { + id: SWIMLANE_TYPE.OVERALL, + label: i18n.translate('xpack.ml.explorer.overallLabel', { + defaultMessage: 'Overall', + }), + }, + { + id: SWIMLANE_TYPE.VIEW_BY, + label: i18n.translate('xpack.ml.explorer.viewByFieldLabel', { + defaultMessage: 'View by {viewByField}, up to {limit} rows', + values: { viewByField: viewBy, limit }, + }), + }, + ]; + + const selection: EuiTableProps['selection'] = { + onSelectionChange: setSelectedItems, + }; + + const noSwimlaneSelected = Object.values(selectedSwimlanes).every((isSelected) => !isSelected); + + return ( + + + + + + + + + + } + > + { + const newSelection = { + ...selectedSwimlanes, + [optionId]: !selectedSwimlanes[optionId as SwimlaneType], + }; + setSelectedSwimlanes(newSelection); + }} + data-test-subj="mlAddToDashboardSwimlaneTypeSelector" + /> + + + + + + } + data-test-subj="mlDashboardSelectionContainer" + > + + + + + + + + { + onClose(async () => { + const selectedDashboardId = selectedItems[0].id; + await addSwimlaneToDashboardCallback(); + await navigateToUrl( + await dashboardService.getDashboardEditUrl(selectedDashboardId) + ); + }); + }} + data-test-subj="mlAddAndEditDashboardButton" + > + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx new file mode 100644 index 0000000000000..b4d32e2af64b8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -0,0 +1,392 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useCallback, useMemo, useRef, useState } from 'react'; +import { isEqual } from 'lodash'; +import DragSelect from 'dragselect'; +import { + EuiPanel, + EuiPopover, + EuiContextMenuPanel, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiTitle, + EuiSpacer, + EuiContextMenuItem, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { DRAG_SELECT_ACTION, VIEW_BY_JOB_LABEL } from './explorer_constants'; +import { AddToDashboardControl } from './add_to_dashboard_control'; +import { useMlKibana } from '../contexts/kibana'; +import { TimeBuckets } from '../util/time_buckets'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; +import { SelectLimit } from './select_limit'; +import { + ALLOW_CELL_RANGE_SELECTION, + dragSelect$, + explorerService, +} from './explorer_dashboard_service'; +import { ExplorerState } from './reducers/explorer_reducer'; +import { hasMatchingPoints } from './has_matching_points'; +import { ExplorerNoInfluencersFound } from './components/explorer_no_influencers_found/explorer_no_influencers_found'; +import { LoadingIndicator } from '../components/loading_indicator'; +import { SwimlaneContainer } from './swimlane_container'; +import { OverallSwimlaneData } from './explorer_utils'; + +function mapSwimlaneOptionsToEuiOptions(options: string[]) { + return options.map((option) => ({ + value: option, + text: option, + })); +} + +interface AnomalyTimelineProps { + explorerState: ExplorerState; + setSelectedCells: (cells?: any) => void; +} + +export const AnomalyTimeline: FC = React.memo( + ({ explorerState, setSelectedCells }) => { + const { + services: { + uiSettings, + application: { capabilities }, + }, + } = useMlKibana(); + + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isAddDashboardsActive, setIsAddDashboardActive] = useState(false); + + const isSwimlaneSelectActive = useRef(false); + // make sure dragSelect is only available if the mouse pointer is actually over a swimlane + const disableDragSelectOnMouseLeave = useRef(true); + + const canEditDashboards = capabilities.dashboard?.createNew ?? false; + + const timeBuckets = useMemo(() => { + return new TimeBuckets({ + 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), + dateFormat: uiSettings.get('dateFormat'), + 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), + }); + }, [uiSettings]); + + const dragSelect = useMemo( + () => + new DragSelect({ + selectorClass: 'ml-swimlane-selector', + selectables: document.querySelectorAll('.sl-cell'), + callback(elements) { + if (elements.length > 1 && !ALLOW_CELL_RANGE_SELECTION) { + elements = [elements[0]]; + } + + if (elements.length > 0) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.NEW_SELECTION, + elements, + }); + } + + disableDragSelectOnMouseLeave.current = true; + }, + onDragStart(e) { + let target = e.target as HTMLElement; + while (target && target !== document.body && !target.classList.contains('sl-cell')) { + target = target.parentNode as HTMLElement; + } + if (ALLOW_CELL_RANGE_SELECTION && target !== document.body) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.DRAG_START, + }); + disableDragSelectOnMouseLeave.current = false; + } + }, + onElementSelect() { + if (ALLOW_CELL_RANGE_SELECTION) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.ELEMENT_SELECT, + }); + } + }, + }), + [] + ); + + const { + filterActive, + filteredFields, + maskAll, + overallSwimlaneData, + selectedCells, + viewByLoadedForTimeFormatted, + viewBySwimlaneData, + viewBySwimlaneDataLoading, + viewBySwimlaneFieldName, + viewBySwimlaneOptions, + swimlaneLimit, + selectedJobs, + } = explorerState; + + const setSwimlaneSelectActive = useCallback((active: boolean) => { + if (isSwimlaneSelectActive.current && !active && disableDragSelectOnMouseLeave.current) { + dragSelect.stop(); + isSwimlaneSelectActive.current = active; + return; + } + if (!isSwimlaneSelectActive.current && active) { + dragSelect.start(); + dragSelect.clearSelection(); + dragSelect.setSelectables(document.querySelectorAll('.sl-cell')); + isSwimlaneSelectActive.current = active; + } + }, []); + const onSwimlaneEnterHandler = () => setSwimlaneSelectActive(true); + const onSwimlaneLeaveHandler = () => setSwimlaneSelectActive(false); + + // Listens to render updates of the swimlanes to update dragSelect + const swimlaneRenderDoneListener = useCallback(() => { + dragSelect.clearSelection(); + dragSelect.setSelectables(document.querySelectorAll('.sl-cell')); + }, []); + + // Listener for click events in the swimlane to load corresponding anomaly data. + const swimlaneCellClick = useCallback((selectedCellsUpdate: any) => { + // If selectedCells is an empty object we clear any existing selection, + // otherwise we save the new selection in AppState and update the Explorer. + if (Object.keys(selectedCellsUpdate).length === 0) { + setSelectedCells(); + } else { + setSelectedCells(selectedCellsUpdate); + } + }, []); + + const showOverallSwimlane = + overallSwimlaneData !== null && + overallSwimlaneData.laneLabels && + overallSwimlaneData.laneLabels.length > 0; + + const showViewBySwimlane = + viewBySwimlaneData !== null && + viewBySwimlaneData.laneLabels && + viewBySwimlaneData.laneLabels.length > 0; + + const menuItems = useMemo(() => { + const items = []; + if (canEditDashboards) { + items.push( + + + + ); + } + return items; + }, [canEditDashboards]); + + return ( + <> + + + + +

+ +

+
+
+ {viewBySwimlaneOptions.length > 0 && ( + <> + + + + + } + display={'columnCompressed'} + > + explorerService.setViewBySwimlaneFieldName(e.target.value)} + /> + + + + + + + } + display={'columnCompressed'} + > + + + + +
+ {viewByLoadedForTimeFormatted && ( + + )} + {viewByLoadedForTimeFormatted === undefined && ( + + )} + {filterActive === true && viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( + + )} +
+
+ + )} + + {menuItems.length > 0 && ( + + + } + isOpen={isMenuOpen} + closePopover={setIsMenuOpen.bind(null, false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + )} +
+ + + +
+ {showOverallSwimlane && ( + explorerService.setSwimlaneContainerWidth(width)} + /> + )} +
+ + {viewBySwimlaneOptions.length > 0 && ( + <> + {showViewBySwimlane && ( + <> + +
+ explorerService.setSwimlaneContainerWidth(width)} + /> +
+ + )} + + {viewBySwimlaneDataLoading && } + + {!showViewBySwimlane && + !viewBySwimlaneDataLoading && + typeof viewBySwimlaneFieldName === 'string' && ( + + )} + + )} +
+ {isAddDashboardsActive && selectedJobs && ( + { + setIsAddDashboardActive(false); + if (callback) { + await callback(); + } + }} + jobIds={selectedJobs.map(({ id }) => id)} + viewBy={viewBySwimlaneFieldName!} + limit={swimlaneLimit} + /> + )} + + ); + }, + (prevProps, nextProps) => { + return isEqual(prevProps.explorerState, nextProps.explorerState); + } +); diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.js b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx similarity index 78% rename from x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.js rename to x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx index 5f54c383e76ad..639c0f7b78504 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.js +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_influencers_found/explorer_no_influencers_found.tsx @@ -4,20 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -/* - * React component for rendering EuiEmptyPrompt when no influencers were found. - */ - -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiEmptyPrompt } from '@elastic/eui'; -export const ExplorerNoInfluencersFound = ({ - viewBySwimlaneFieldName, - showFilterMessage = false, -}) => ( +/* + * React component for rendering EuiEmptyPrompt when no influencers were found. + */ +export const ExplorerNoInfluencersFound: FC<{ + viewBySwimlaneFieldName: string; + showFilterMessage?: boolean; +}> = ({ viewBySwimlaneFieldName, showFilterMessage = false }) => ( ); - -ExplorerNoInfluencersFound.propTypes = { - viewBySwimlaneFieldName: PropTypes.string.isRequired, - showFilterMessage: PropTypes.bool, -}; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 1a5a9a9d82862..71c96840d1b57 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -9,10 +9,9 @@ */ import PropTypes from 'prop-types'; -import React, { createRef } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import DragSelect from 'dragselect/dist/ds.min.js'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -26,34 +25,23 @@ import { EuiPageBody, EuiPageHeader, EuiPageHeaderSection, - EuiSelect, EuiSpacer, EuiTitle, } from '@elastic/eui'; import { AnnotationFlyout } from '../components/annotations/annotation_flyout'; import { AnnotationsTable } from '../components/annotations/annotations_table'; -import { - ExplorerNoInfluencersFound, - ExplorerNoJobsFound, - ExplorerNoResultsFound, -} from './components'; -import { ExplorerSwimlane } from './explorer_swimlane'; -import { getTimeBucketsFromCache } from '../util/time_buckets'; +import { ExplorerNoJobsFound, ExplorerNoResultsFound } from './components'; import { DatePickerWrapper } from '../components/navigation_menu/date_picker_wrapper'; import { InfluencersList } from '../components/influencers_list'; -import { - ALLOW_CELL_RANGE_SELECTION, - dragSelect$, - explorerService, -} from './explorer_dashboard_service'; +import { explorerService } from './explorer_dashboard_service'; import { AnomalyResultsViewSelector } from '../components/anomaly_results_view_selector'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; import { NavigationMenu } from '../components/navigation_menu'; import { CheckboxShowCharts } from '../components/controls/checkbox_showcharts'; import { JobSelector } from '../components/job_selector'; import { SelectInterval } from '../components/controls/select_interval/select_interval'; -import { SelectLimit, limit$ } from './select_limit/select_limit'; +import { limit$ } from './select_limit/select_limit'; import { SelectSeverity } from '../components/controls/select_severity/select_severity'; import { ExplorerQueryBar, @@ -67,14 +55,9 @@ import { escapeParens, escapeDoubleQuotes, } from './explorer_utils'; -import { getSwimlaneContainerWidth } from './legacy_utils'; +import { AnomalyTimeline } from './anomaly_timeline'; -import { - DRAG_SELECT_ACTION, - FILTER_ACTION, - SWIMLANE_TYPE, - VIEW_BY_JOB_LABEL, -} from './explorer_constants'; +import { FILTER_ACTION } from './explorer_constants'; // Explorer Charts import { ExplorerChartsContainer } from './explorer_charts/explorer_charts_container'; @@ -82,17 +65,7 @@ import { ExplorerChartsContainer } from './explorer_charts/explorer_charts_conta // Anomalies Table import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; -import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { getTimefilter, getToastNotifications } from '../util/dependency_cache'; -import { MlTooltipComponent } from '../components/chart_tooltip'; -import { hasMatchingPoints } from './has_matching_points'; - -function mapSwimlaneOptionsToEuiOptions(options) { - return options.map((option) => ({ - value: option, - text: option, - })); -} const ExplorerPage = ({ children, @@ -105,9 +78,8 @@ const ExplorerPage = ({ queryString, filterIconTriggeredQuery, updateLanguage, - resizeRef, }) => ( -
+
@@ -171,108 +143,18 @@ export class Explorer extends React.Component { state = { filterIconTriggeredQuery: undefined, language: DEFAULT_QUERY_LANG }; _unsubscribeAll = new Subject(); - // make sure dragSelect is only available if the mouse pointer is actually over a swimlane - disableDragSelectOnMouseLeave = true; - - dragSelect = new DragSelect({ - selectorClass: 'ml-swimlane-selector', - selectables: document.getElementsByClassName('sl-cell'), - callback(elements) { - if (elements.length > 1 && !ALLOW_CELL_RANGE_SELECTION) { - elements = [elements[0]]; - } - - if (elements.length > 0) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.NEW_SELECTION, - elements, - }); - } - - this.disableDragSelectOnMouseLeave = true; - }, - onDragStart(e) { - let target = e.target; - while (target && target !== document.body && !target.classList.contains('sl-cell')) { - target = target.parentNode; - } - if (ALLOW_CELL_RANGE_SELECTION && target !== document.body) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.DRAG_START, - }); - this.disableDragSelectOnMouseLeave = false; - } - }, - onElementSelect() { - if (ALLOW_CELL_RANGE_SELECTION) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.ELEMENT_SELECT, - }); - } - }, - }); - - // Listens to render updates of the swimlanes to update dragSelect - swimlaneRenderDoneListener = () => { - this.dragSelect.clearSelection(); - this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); - }; - - resizeRef = createRef(); - resizeChecker = undefined; - resizeHandler = () => { - explorerService.setSwimlaneContainerWidth(getSwimlaneContainerWidth()); - }; componentDidMount() { limit$.pipe(takeUntil(this._unsubscribeAll)).subscribe(explorerService.setSwimlaneLimit); - - // Required to redraw the time series chart when the container is resized. - this.resizeChecker = new ResizeChecker(this.resizeRef.current); - this.resizeChecker.on('resize', this.resizeHandler); - - this.timeBuckets = getTimeBucketsFromCache(); } componentWillUnmount() { this._unsubscribeAll.next(); this._unsubscribeAll.complete(); - this.resizeChecker.destroy(); - } - - resetCache() { - this.anomaliesTablePreviousArgs = null; } viewByChangeHandler = (e) => explorerService.setViewBySwimlaneFieldName(e.target.value); - isSwimlaneSelectActive = false; - onSwimlaneEnterHandler = () => this.setSwimlaneSelectActive(true); - onSwimlaneLeaveHandler = () => this.setSwimlaneSelectActive(false); - setSwimlaneSelectActive = (active) => { - if (this.isSwimlaneSelectActive && !active && this.disableDragSelectOnMouseLeave) { - this.dragSelect.stop(); - this.isSwimlaneSelectActive = active; - return; - } - if (!this.isSwimlaneSelectActive && active) { - this.dragSelect.start(); - this.dragSelect.clearSelection(); - this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); - this.isSwimlaneSelectActive = active; - } - }; - - // Listener for click events in the swimlane to load corresponding anomaly data. - swimlaneCellClick = (selectedCells) => { - // If selectedCells is an empty object we clear any existing selection, - // otherwise we save the new selection in AppState and update the Explorer. - if (Object.keys(selectedCells).length === 0) { - this.props.setSelectedCells(); - } else { - this.props.setSelectedCells(selectedCells); - } - }; // Escape regular parens from fieldName as that portion of the query is not wrapped in double quotes // and will cause a syntax error when called with getKqlQueryValues applyFilter = (fieldName, fieldValue, action) => { @@ -339,24 +221,16 @@ export class Explorer extends React.Component { annotationsData, chartsData, filterActive, - filteredFields, filterPlaceHolder, indexPattern, influencers, loading, - maskAll, noInfluencersConfigured, overallSwimlaneData, queryString, selectedCells, selectedJobs, - swimlaneContainerWidth, tableData, - viewByLoadedForTimeFormatted, - viewBySwimlaneData, - viewBySwimlaneDataLoading, - viewBySwimlaneFieldName, - viewBySwimlaneOptions, } = this.props.explorerState; const jobSelectorProps = { @@ -378,7 +252,6 @@ export class Explorer extends React.Component { indexPattern={indexPattern} queryString={queryString} updateLanguage={this.updateLanguage} - resizeRef={this.resizeRef} > + ); @@ -399,7 +272,7 @@ export class Explorer extends React.Component { if (noJobsFound && hasResults === false) { return ( - + ); @@ -408,15 +281,6 @@ export class Explorer extends React.Component { const mainColumnWidthClassName = noInfluencersConfigured === true ? 'col-xs-12' : 'col-xs-10'; const mainColumnClasses = `column ${mainColumnWidthClassName}`; - const showOverallSwimlane = - overallSwimlaneData !== null && - overallSwimlaneData.laneLabels && - overallSwimlaneData.laneLabels.length > 0; - const showViewBySwimlane = - viewBySwimlaneData !== null && - viewBySwimlaneData.laneLabels && - viewBySwimlaneData.laneLabels.length > 0; - const timefilter = getTimefilter(); const bounds = timefilter.getActiveBounds(); @@ -431,7 +295,6 @@ export class Explorer extends React.Component { indexPattern={indexPattern} queryString={queryString} updateLanguage={this.updateLanguage} - resizeRef={this.resizeRef} >
{noInfluencersConfigured && ( @@ -462,142 +325,12 @@ export class Explorer extends React.Component { )}
- -

- -

-
- -
- {showOverallSwimlane && ( - - {(tooltipService) => ( - - )} - - )} -
- - {viewBySwimlaneOptions.length > 0 && ( - <> - - - - - - - - - - - - - -
- {viewByLoadedForTimeFormatted && ( - - )} - {viewByLoadedForTimeFormatted === undefined && ( - - )} - {filterActive === true && viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( - - )} -
-
-
-
- - {showViewBySwimlane && ( - <> - -
- - {(tooltipService) => ( - - )} - -
- - )} - - {viewBySwimlaneDataLoading && } - - {!showViewBySwimlane && - !viewBySwimlaneDataLoading && - viewBySwimlaneFieldName !== null && ( - - )} - - )} + + + {annotationsData.length > 0 && ( <> diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts index 1cfd29e2f60d2..d1adf8c7ad744 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts @@ -37,10 +37,12 @@ export const FILTER_ACTION = { REMOVE: '-', }; -export enum SWIMLANE_TYPE { - OVERALL = 'overall', - VIEW_BY = 'viewBy', -} +export const SWIMLANE_TYPE = { + OVERALL: 'overall', + VIEW_BY: 'viewBy', +} as const; + +export type SwimlaneType = typeof SWIMLANE_TYPE[keyof typeof SWIMLANE_TYPE]; export const CHART_TYPE = { EVENT_DISTRIBUTION: 'event_distribution', diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx index 18b5de1d51f9c..4e6dcdcc5129c 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_swimlane.tsx @@ -22,7 +22,7 @@ import { numTicksForDateFormat } from '../util/chart_utils'; import { getSeverityColor } from '../../../common/util/anomaly_utils'; import { mlEscape } from '../util/string_utils'; import { ALLOW_CELL_RANGE_SELECTION, dragSelect$ } from './explorer_dashboard_service'; -import { DRAG_SELECT_ACTION } from './explorer_constants'; +import { DRAG_SELECT_ACTION, SwimlaneType } from './explorer_constants'; import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control'; import { TimeBuckets as TimeBucketsClass } from '../util/time_buckets'; import { @@ -58,7 +58,7 @@ export interface ExplorerSwimlaneProps { timeBuckets: InstanceType; swimlaneCellClick?: Function; swimlaneData: OverallSwimlaneData; - swimlaneType: string; + swimlaneType: SwimlaneType; selection?: { lanes: any[]; type: string; diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index 0a2dbf5bcff35..4e1a2af9b13a6 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -16,8 +16,9 @@ import { AnomaliesTableData, ExplorerJob, AppStateSelectedCells, - SwimlaneData, TimeRangeBounds, + OverallSwimlaneData, + SwimlaneData, } from '../../explorer_utils'; export interface ExplorerState { @@ -35,7 +36,7 @@ export interface ExplorerState { loading: boolean; maskAll: boolean; noInfluencersConfigured: boolean; - overallSwimlaneData: SwimlaneData; + overallSwimlaneData: SwimlaneData | OverallSwimlaneData; queryString: string; selectedCells: AppStateSelectedCells | undefined; selectedJobs: ExplorerJob[] | null; @@ -45,7 +46,7 @@ export interface ExplorerState { tableData: AnomaliesTableData; tableQueryString: string; viewByLoadedForTimeFormatted: string | null; - viewBySwimlaneData: SwimlaneData; + viewBySwimlaneData: SwimlaneData | OverallSwimlaneData; viewBySwimlaneDataLoading: boolean; viewBySwimlaneFieldName?: string; viewBySwimlaneOptions: string[]; diff --git a/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx b/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx index 7f7a8fc5a70bd..7a2df1a0f0535 100644 --- a/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx +++ b/x-pack/plugins/ml/public/application/explorer/select_limit/select_limit.tsx @@ -36,5 +36,5 @@ export const SelectLimit = () => { setLimit(parseInt(e.target.value, 10)); } - return ; + return ; }; diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx new file mode 100644 index 0000000000000..57d1fd81000b7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useCallback, useState } from 'react'; +import { EuiResizeObserver, EuiText } from '@elastic/eui'; + +import { throttle } from 'lodash'; +import { + ExplorerSwimlane, + ExplorerSwimlaneProps, +} from '../../application/explorer/explorer_swimlane'; + +import { MlTooltipComponent } from '../../application/components/chart_tooltip'; + +const RESIZE_THROTTLE_TIME_MS = 500; + +export const SwimlaneContainer: FC< + Omit & { + onResize: (width: number) => void; + } +> = ({ children, onResize, ...props }) => { + const [chartWidth, setChartWidth] = useState(0); + + const resizeHandler = useCallback( + throttle((e: { width: number; height: number }) => { + const labelWidth = 200; + setChartWidth(e.width - labelWidth); + onResize(e.width); + }, RESIZE_THROTTLE_TIME_MS), + [] + ); + + return ( + + {(resizeRef) => ( +
{ + resizeRef(el); + }} + > +
+ + + {(tooltipService) => ( + + )} + + +
+
+ )} +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js index 817715dbf6413..2f40941fd20fe 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/forecasts_table/forecasts_table.js @@ -21,6 +21,7 @@ import { import { formatDate, formatNumber } from '@elastic/eui/lib/services/format'; import { FORECAST_REQUEST_STATE } from '../../../../../../../common/constants/states'; +import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; import { addItemToRecentlyAccessed } from '../../../../../util/recently_accessed'; import { mlForecastService } from '../../../../../services/forecast_service'; import { i18n } from '@kbn/i18n'; @@ -31,7 +32,6 @@ import { } from '../../../../../../../common/util/job_utils'; const MAX_FORECASTS = 500; -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; /** * Table component for rendering the lists of forecasts run on an ML job. diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js index 9194f7537cf3d..883ddfca70cd7 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js @@ -8,8 +8,8 @@ import numeral from '@elastic/numeral'; import { formatDate } from '@elastic/eui/lib/services/format'; import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; import { toLocaleString } from '../../../../util/string_utils'; +import { TIME_FORMAT } from '../../../../../../common/constants/time_format'; -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; const DATA_FORMAT = '0.0 b'; function formatData(txt) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 56da4f1e0ff84..9a5cea62cf6ff 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -14,6 +14,7 @@ import { JsonPane } from './json_tab'; import { DatafeedPreviewPane } from './datafeed_preview_tab'; import { AnnotationsTable } from '../../../../components/annotations/annotations_table'; import { AnnotationFlyout } from '../../../../components/annotations/annotation_flyout'; +import { ModelSnapshotTable } from '../../../../components/model_snapshots'; import { ForecastsTable } from './forecasts_table'; import { JobDetailsPane } from './job_details_pane'; import { JobMessagesPane } from './job_messages_pane'; @@ -25,7 +26,7 @@ export class JobDetails extends Component { this.state = {}; if (this.props.addYourself) { - this.props.addYourself(props.jobId, this); + this.props.addYourself(props.jobId, (j) => this.updateJob(j)); } } @@ -33,9 +34,8 @@ export class JobDetails extends Component { this.props.removeYourself(this.props.jobId); } - static getDerivedStateFromProps(props) { - const { job, loading } = props; - return { job, loading }; + updateJob(job) { + this.setState({ job }); } render() { @@ -64,8 +64,7 @@ export class JobDetails extends Component { datafeedTimingStats, } = extractJobDetails(job); - const { showFullDetails } = this.props; - + const { showFullDetails, refreshJobList } = this.props; const tabs = [ { id: 'job-settings', @@ -175,6 +174,19 @@ export class JobDetails extends Component { ), }); + + tabs.push({ + id: 'modelSnapshots', + 'data-test-subj': 'mlJobListTab-modelSnapshots', + name: i18n.translate('xpack.ml.jobsList.jobDetails.tabs.modelSnapshotsLabel', { + defaultMessage: 'Model snapshots', + }), + content: ( + + + + ), + }); } return ( @@ -191,4 +203,5 @@ JobDetails.propTypes = { addYourself: PropTypes.func.isRequired, removeYourself: PropTypes.func.isRequired, showFullDetails: PropTypes.bool, + refreshJobList: PropTypes.func, }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 0afaca3ec12e1..23b68551ca0f5 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -15,6 +15,7 @@ import { ResultLinks, actionsMenuContent } from '../job_actions'; import { JobDescription } from './job_description'; import { JobIcon } from '../../../../components/job_message_icon'; import { getJobIdUrl } from '../../../../util/get_job_id_url'; +import { TIME_FORMAT } from '../../../../../../common/constants/time_format'; import { EuiBadge, EuiBasicTable, EuiButtonIcon, EuiLink, EuiScreenReaderOnly } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -22,7 +23,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; const PAGE_SIZE = 10; const PAGE_SIZE_OPTIONS = [10, 25, 50]; -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; // 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page export class JobsList extends Component { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index bb9e532245d6d..a3b6cb39815a3 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -112,6 +112,7 @@ export class JobsListView extends Component { addYourself={this.addUpdateFunction} removeYourself={this.removeUpdateFunction} showFullDetails={this.props.isManagementTable !== true} + refreshJobList={this.onRefreshClick} /> ); } else { @@ -121,6 +122,7 @@ export class JobsListView extends Component { addYourself={this.addUpdateFunction} removeYourself={this.removeUpdateFunction} showFullDetails={this.props.isManagementTable !== true} + refreshJobList={this.onRefreshClick} /> ); } @@ -143,10 +145,13 @@ export class JobsListView extends Component { addYourself={this.addUpdateFunction} removeYourself={this.removeUpdateFunction} showFullDetails={this.props.isManagementTable !== true} + refreshJobList={this.onRefreshClick} /> ); } - this.setState({ itemIdToExpandedRowMap }); + this.setState({ itemIdToExpandedRowMap }, () => { + this.updateFunctions[jobId](job); + }); }); }) .catch((error) => { @@ -254,7 +259,7 @@ export class JobsListView extends Component { ); Object.keys(this.updateFunctions).forEach((j) => { - this.updateFunctions[j].setState({ job: fullJobsList[j] }); + this.updateFunctions[j](fullJobsList[j]); }); jobs.forEach((job) => { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js index 55c87bbc90b10..8cf244c3c7d8a 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js @@ -13,8 +13,7 @@ import { EuiDatePicker, EuiFieldText } from '@elastic/eui'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - -const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; +import { TIME_FORMAT } from '../../../../../../../common/constants/time_format'; export class TimeRangeSelector extends Component { constructor(props) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx index 2fb8ea2820b29..bd6fedd4ba21c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx @@ -5,13 +5,22 @@ */ import React, { FC } from 'react'; -import { BarSeries, Chart, ScaleType, Settings, TooltipType } from '@elastic/charts'; +import { + HistogramBarSeries, + Chart, + ScaleType, + Settings, + TooltipType, + BrushEndListener, + PartialTheme, +} from '@elastic/charts'; import { Axes } from '../common/axes'; import { LineChartPoint } from '../../../../common/chart_loader'; import { Anomaly } from '../../../../common/results_loader'; import { useChartColors } from '../common/settings'; import { LoadingWrapper } from '../loading_wrapper'; import { Anomalies } from '../common/anomalies'; +import { OverlayRange } from './overlay_range'; interface Props { eventRateChartData: LineChartPoint[]; @@ -21,6 +30,13 @@ interface Props { showAxis?: boolean; loading?: boolean; fadeChart?: boolean; + overlayRanges?: Array<{ + start: number; + end: number; + color: string; + showMarker?: boolean; + }>; + onBrushEnd?: BrushEndListener; } export const EventRateChart: FC = ({ @@ -31,10 +47,16 @@ export const EventRateChart: FC = ({ showAxis, loading = false, fadeChart, + overlayRanges, + onBrushEnd, }) => { const { EVENT_RATE_COLOR_WITH_ANOMALIES, EVENT_RATE_COLOR } = useChartColors(); const barColor = fadeChart ? EVENT_RATE_COLOR_WITH_ANOMALIES : EVENT_RATE_COLOR; + const theme: PartialTheme = { + scales: { histogramPadding: 0.2 }, + }; + return (
= ({ {showAxis === true && } - + {onBrushEnd === undefined ? ( + + ) : ( + + )} + + {overlayRanges && + overlayRanges.map((range, i) => ( + + ))} + - = ({ + overlayKey, + eventRateChartData, + start, + end, + color, + showMarker = true, +}) => { + const maxHeight = Math.max(...eventRateChartData.map((e) => e.value)); + + return ( + <> + + +
+
+ +
+
+ {formatDate(start, TIME_FORMAT)} +
+
+ + ) : undefined + } + /> + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/create_result_callout.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/create_result_callout.tsx index 9d0cf705aaba6..4602ceeec905f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/create_result_callout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/create_result_callout.tsx @@ -67,7 +67,7 @@ export const CreateResultCallout: FC = memo( color="primary" fill={false} aria-label={i18n.translate( - 'xpack.ml.newJi18n(ob.recognize.jobsCreationFailed.resetButtonAriaLabel', + 'xpack.ml.newJob.recognize.jobsCreationFailed.resetButtonAriaLabel', { defaultMessage: 'Reset' } )} onClick={onReset} diff --git a/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts b/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts new file mode 100644 index 0000000000000..6cab23eb187c7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/services/dashboard_service.test.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 { dashboardServiceProvider } from './dashboard_service'; +import { savedObjectsServiceMock } from '../../../../../../src/core/public/mocks'; +import { SavedObjectDashboard } from '../../../../../../src/plugins/dashboard/public/saved_dashboards'; +import { + DashboardUrlGenerator, + SavedDashboardPanel, +} from '../../../../../../src/plugins/dashboard/public'; + +jest.mock('@elastic/eui', () => { + return { + htmlIdGenerator: jest.fn(() => { + return jest.fn(() => 'test-panel-id'); + }), + }; +}); + +describe('DashboardService', () => { + const mockSavedObjectClient = savedObjectsServiceMock.createStartContract().client; + const dashboardUrlGenerator = ({ + createUrl: jest.fn(), + } as unknown) as DashboardUrlGenerator; + const dashboardService = dashboardServiceProvider( + mockSavedObjectClient, + '8.0.0', + dashboardUrlGenerator + ); + + test('should fetch dashboard', () => { + // act + dashboardService.fetchDashboards('test'); + // assert + expect(mockSavedObjectClient.find).toHaveBeenCalledWith({ + type: 'dashboard', + perPage: 10, + search: `test*`, + searchFields: ['title^3', 'description'], + }); + }); + + test('should attach panel to the dashboard', () => { + // act + dashboardService.attachPanels( + 'test-dashboard', + ({ + title: 'ML Test', + hits: 0, + description: '', + panelsJSON: JSON.stringify([ + { + version: '8.0.0', + type: 'ml_anomaly_swimlane', + gridData: { x: 0, y: 0, w: 24, h: 15, i: 'i63c960b1-ab1b-11ea-809d-f5c60c43347f' }, + panelIndex: 'i63c960b1-ab1b-11ea-809d-f5c60c43347f', + embeddableConfig: { + title: 'Panel test!', + jobIds: ['cw_multi_1'], + swimlaneType: 'overall', + }, + title: 'Panel test!', + }, + { + version: '8.0.0', + type: 'ml_anomaly_swimlane', + gridData: { x: 24, y: 0, w: 24, h: 15, i: '0aa334bd-8308-4ded-9462-80dbd37680ee' }, + panelIndex: '0aa334bd-8308-4ded-9462-80dbd37680ee', + embeddableConfig: { + title: 'ML anomaly swimlane for fb_population_1', + jobIds: ['fb_population_1'], + limit: 5, + swimlaneType: 'overall', + }, + title: 'ML anomaly swimlane for fb_population_1', + }, + { + version: '8.0.0', + gridData: { x: 0, y: 15, w: 24, h: 15, i: 'abd36eb7-4774-4216-891e-12100752b46d' }, + panelIndex: 'abd36eb7-4774-4216-891e-12100752b46d', + embeddableConfig: {}, + panelRefName: 'panel_2', + }, + ]), + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: false, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, + } as unknown) as SavedObjectDashboard, + [{ title: 'Test title', type: 'test-panel', embeddableConfig: { testConfig: '' } }] + ); + // assert + expect(mockSavedObjectClient.update).toHaveBeenCalledWith('dashboard', 'test-dashboard', { + title: 'ML Test', + hits: 0, + description: '', + panelsJSON: JSON.stringify([ + { + version: '8.0.0', + type: 'ml_anomaly_swimlane', + gridData: { x: 0, y: 0, w: 24, h: 15, i: 'i63c960b1-ab1b-11ea-809d-f5c60c43347f' }, + panelIndex: 'i63c960b1-ab1b-11ea-809d-f5c60c43347f', + embeddableConfig: { + title: 'Panel test!', + jobIds: ['cw_multi_1'], + swimlaneType: 'overall', + }, + title: 'Panel test!', + }, + { + version: '8.0.0', + type: 'ml_anomaly_swimlane', + gridData: { x: 24, y: 0, w: 24, h: 15, i: '0aa334bd-8308-4ded-9462-80dbd37680ee' }, + panelIndex: '0aa334bd-8308-4ded-9462-80dbd37680ee', + embeddableConfig: { + title: 'ML anomaly swimlane for fb_population_1', + jobIds: ['fb_population_1'], + limit: 5, + swimlaneType: 'overall', + }, + title: 'ML anomaly swimlane for fb_population_1', + }, + { + version: '8.0.0', + gridData: { x: 0, y: 15, w: 24, h: 15, i: 'abd36eb7-4774-4216-891e-12100752b46d' }, + panelIndex: 'abd36eb7-4774-4216-891e-12100752b46d', + embeddableConfig: {}, + panelRefName: 'panel_2', + }, + { + panelIndex: 'test-panel-id', + embeddableConfig: { testConfig: '' }, + title: 'Test title', + type: 'test-panel', + version: '8.0.0', + gridData: { h: 15, i: 'test-panel-id', w: 24, x: 24, y: 15 }, + }, + ]), + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: false, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, + }); + }); + + test('should generate edit url to the dashboard', () => { + dashboardService.getDashboardEditUrl('test-id'); + expect(dashboardUrlGenerator.createUrl).toHaveBeenCalledWith({ + dashboardId: 'test-id', + useHash: false, + viewMode: 'edit', + }); + }); + + test('should find the panel positioned at the end', () => { + expect( + dashboardService.getLastPanel([ + { gridData: { y: 15, x: 7 } }, + { gridData: { y: 17, x: 9 } }, + { gridData: { y: 15, x: 1 } }, + { gridData: { y: 17, x: 10 } }, + { gridData: { y: 15, x: 22 } }, + { gridData: { y: 17, x: 9 } }, + ] as SavedDashboardPanel[]) + ).toEqual({ gridData: { y: 17, x: 10 } }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/services/dashboard_service.ts b/x-pack/plugins/ml/public/application/services/dashboard_service.ts new file mode 100644 index 0000000000000..7f2bb71d18eb9 --- /dev/null +++ b/x-pack/plugins/ml/public/application/services/dashboard_service.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/public'; +import { htmlIdGenerator } from '@elastic/eui'; +import { useMemo } from 'react'; +import { + DASHBOARD_APP_URL_GENERATOR, + DashboardUrlGenerator, + SavedDashboardPanel, + SavedObjectDashboard, +} from '../../../../../../src/plugins/dashboard/public'; +import { useMlKibana } from '../contexts/kibana'; +import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; + +export type DashboardService = ReturnType; + +export function dashboardServiceProvider( + savedObjectClient: SavedObjectsClientContract, + kibanaVersion: string, + dashboardUrlGenerator: DashboardUrlGenerator +) { + const generateId = htmlIdGenerator(); + const DEFAULT_PANEL_WIDTH = 24; + const DEFAULT_PANEL_HEIGHT = 15; + + return { + /** + * Fetches dashboards + */ + async fetchDashboards(query?: string) { + return await savedObjectClient.find({ + type: 'dashboard', + perPage: 10, + search: query ? `${query}*` : '', + searchFields: ['title^3', 'description'], + }); + }, + /** + * Resolves the last positioned panel from the collection. + */ + getLastPanel(panels: SavedDashboardPanel[]): SavedDashboardPanel | null { + return panels.length > 0 + ? panels.reduce((prev, current) => + prev.gridData.y >= current.gridData.y + ? prev.gridData.y === current.gridData.y + ? prev.gridData.x > current.gridData.x + ? prev + : current + : prev + : current + ) + : null; + }, + /** + * Attaches embeddable panels to the dashboard + */ + async attachPanels( + dashboardId: string, + dashboardAttributes: SavedObjectDashboard, + panelsData: Array> + ) { + const panels = JSON.parse(dashboardAttributes.panelsJSON) as SavedDashboardPanel[]; + const version = kibanaVersion; + const rowWidth = DEFAULT_PANEL_WIDTH * 2; + + for (const panelData of panelsData) { + const panelIndex = generateId(); + const lastPanel = this.getLastPanel(panels); + + const xOffset = lastPanel ? lastPanel.gridData.w + lastPanel.gridData.x : 0; + const availableRowSpace = rowWidth - xOffset; + const xPosition = availableRowSpace - DEFAULT_PANEL_WIDTH >= 0 ? xOffset : 0; + + panels.push({ + panelIndex, + embeddableConfig: panelData.embeddableConfig as { [key: string]: any }, + title: panelData.title, + type: panelData.type, + version, + gridData: { + h: DEFAULT_PANEL_HEIGHT, + i: panelIndex, + w: DEFAULT_PANEL_WIDTH, + x: xPosition, + y: lastPanel + ? xPosition > 0 + ? lastPanel.gridData.y + : lastPanel.gridData.y + lastPanel.gridData.h + : 0, + }, + }); + } + + await savedObjectClient.update('dashboard', dashboardId, { + ...dashboardAttributes, + panelsJSON: JSON.stringify(panels), + }); + }, + /** + * Generates dashboard url with edit mode + */ + async getDashboardEditUrl(dashboardId: string) { + return await dashboardUrlGenerator.createUrl({ + dashboardId, + useHash: false, + viewMode: ViewMode.EDIT, + }); + }, + }; +} + +/** + * Hook to use {@link DashboardService} in react components + */ +export function useDashboardService(): DashboardService { + const { + services: { + savedObjects: { client: savedObjectClient }, + kibanaVersion, + share: { urlGenerators }, + }, + } = useMlKibana(); + return useMemo( + () => + dashboardServiceProvider( + savedObjectClient, + kibanaVersion, + urlGenerators.getUrlGenerator(DASHBOARD_APP_URL_GENERATOR) + ), + [savedObjectClient, kibanaVersion] + ); +} diff --git a/x-pack/plugins/ml/public/application/services/http_service.ts b/x-pack/plugins/ml/public/application/services/http_service.ts index 7144411c2885d..bd927dc0e3011 100644 --- a/x-pack/plugins/ml/public/application/services/http_service.ts +++ b/x-pack/plugins/ml/public/application/services/http_service.ts @@ -37,6 +37,8 @@ function getFetchOptions( /** * Function for making HTTP requests to Kibana's backend. * Wrapper for Kibana's HttpHandler. + * + * @deprecated use {@link HttpService} instead */ export async function http(options: HttpFetchOptionsWithPath): Promise { const { path, fetchOptions } = getFetchOptions(options); @@ -46,6 +48,8 @@ export async function http(options: HttpFetchOptionsWithPath): Promise { /** * Function for making HTTP requests to Kibana's backend which returns an Observable * with request cancellation support. + * + * @deprecated use {@link HttpService} instead */ export function http$(options: HttpFetchOptionsWithPath): Observable { const { path, fetchOptions } = getFetchOptions(options); @@ -55,7 +59,7 @@ export function http$(options: HttpFetchOptionsWithPath): Observable { /** * Creates an Observable from Kibana's HttpHandler. */ -export function fromHttpHandler(input: string, init?: RequestInit): Observable { +function fromHttpHandler(input: string, init?: RequestInit): Observable { return new Observable((subscriber) => { const controller = new AbortController(); const signal = controller.signal; diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index a3be479571702..6c0f393c267aa 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -13,6 +13,7 @@ import { ml } from './ml_api_service'; import { mlMessageBarService } from '../components/messagebar'; import { isWebUrl } from '../util/url_utils'; import { ML_DATA_PREVIEW_COUNT } from '../../../common/util/job_utils'; +import { TIME_FORMAT } from '../../../common/constants/time_format'; import { parseInterval } from '../../../common/util/parse_interval'; const msgs = mlMessageBarService; @@ -929,10 +930,8 @@ function createResultsUrlForJobs(jobsList, resultsPage) { } } - const timeFormat = 'YYYY-MM-DD HH:mm:ss'; - - const fromString = moment(from).format(timeFormat); // Defaults to 'now' if 'from' is undefined - const toString = moment(to).format(timeFormat); // Defaults to 'now' if 'to' is undefined + const fromString = moment(from).format(TIME_FORMAT); // Defaults to 'now' if 'from' is undefined + const toString = moment(to).format(TIME_FORMAT); // Defaults to 'now' if 'to' is undefined const jobIds = jobsList.map((j) => j.id); return createResultsUrl(jobIds, fromString, toString, resultsPage); diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 6e3fd08e90e38..6d32fca6a645c 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -5,12 +5,13 @@ */ import { Observable } from 'rxjs'; -import { http, http$ } from '../http_service'; +import { HttpStart } from 'kibana/public'; +import { HttpService } from '../http_service'; import { annotations } from './annotations'; import { dataFrameAnalytics } from './data_frame_analytics'; import { filters } from './filters'; -import { results } from './results'; +import { resultsApiProvider } from './results'; import { jobs } from './jobs'; import { fileDatavisualizer } from './datavisualizer'; import { MlServerDefaults, MlServerLimits } from '../../../../common/types/ml_server_info'; @@ -23,10 +24,12 @@ import { CombinedJob, Detector, AnalysisConfig, + ModelSnapshot, } from '../../../../common/types/anomaly_detection_jobs'; import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; import { FieldRequestConfig } from '../../datavisualizer/index_based/common'; import { DataRecognizerConfigResponse, Module } from '../../../../common/types/modules'; +import { getHttp } from '../../util/dependency_cache'; export interface MlInfoResponse { defaults: MlServerDefaults; @@ -77,317 +80,339 @@ export interface CardinalityModelPlotHigh { export type CardinalityValidationResult = SuccessCardinality | CardinalityModelPlotHigh; export type CardinalityValidationResults = CardinalityValidationResult[]; +export interface GetModelSnapshotsResponse { + count: number; + model_snapshots: ModelSnapshot[]; +} + export function basePath() { return '/api/ml'; } -export const ml = { - getJobs(obj?: { jobId?: string }) { - const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; - return http({ - path: `${basePath()}/anomaly_detectors${jobId}`, - }); - }, - - getJobStats(obj: { jobId?: string }) { - const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; - return http({ - path: `${basePath()}/anomaly_detectors${jobId}/_stats`, - }); - }, - - addJob({ jobId, job }: { jobId: string; job: Job }) { - const body = JSON.stringify(job); - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}`, - method: 'PUT', - body, - }); - }, - - openJob({ jobId }: { jobId: string }) { - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}/_open`, - method: 'POST', - }); - }, - - closeJob({ jobId }: { jobId: string }) { - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}/_close`, - method: 'POST', - }); - }, - - deleteJob({ jobId }: { jobId: string }) { - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}`, - method: 'DELETE', - }); - }, - - forceDeleteJob({ jobId }: { jobId: string }) { - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}?force=true`, - method: 'DELETE', - }); - }, - - updateJob({ jobId, job }: { jobId: string; job: Job }) { - const body = JSON.stringify(job); - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}/_update`, - method: 'POST', - body, - }); - }, - - estimateBucketSpan(obj: BucketSpanEstimatorData) { - const body = JSON.stringify(obj); - return http({ - path: `${basePath()}/validate/estimate_bucket_span`, - method: 'POST', - body, - }); - }, - - validateJob(payload: { - job: Job; - duration: { - start?: number; - end?: number; - }; - fields?: any[]; - }) { - const body = JSON.stringify(payload); - return http({ - path: `${basePath()}/validate/job`, - method: 'POST', - body, - }); - }, - - validateCardinality$(job: CombinedJob): Observable { - const body = JSON.stringify(job); - return http$({ - path: `${basePath()}/validate/cardinality`, - method: 'POST', - body, - }); - }, - - getDatafeeds(obj: { datafeedId: string }) { - const datafeedId = obj && obj.datafeedId ? `/${obj.datafeedId}` : ''; - return http({ - path: `${basePath()}/datafeeds${datafeedId}`, - }); - }, - - getDatafeedStats(obj: { datafeedId: string }) { - const datafeedId = obj && obj.datafeedId ? `/${obj.datafeedId}` : ''; - return http({ - path: `${basePath()}/datafeeds${datafeedId}/_stats`, - }); - }, - - addDatafeed({ datafeedId, datafeedConfig }: { datafeedId: string; datafeedConfig: Datafeed }) { - const body = JSON.stringify(datafeedConfig); - return http({ - path: `${basePath()}/datafeeds/${datafeedId}`, - method: 'PUT', - body, - }); - }, - - updateDatafeed({ datafeedId, datafeedConfig }: { datafeedId: string; datafeedConfig: Datafeed }) { - const body = JSON.stringify(datafeedConfig); - return http({ - path: `${basePath()}/datafeeds/${datafeedId}/_update`, - method: 'POST', - body, - }); - }, - - deleteDatafeed({ datafeedId }: { datafeedId: string }) { - return http({ - path: `${basePath()}/datafeeds/${datafeedId}`, - method: 'DELETE', - }); - }, - - forceDeleteDatafeed({ datafeedId }: { datafeedId: string }) { - return http({ - path: `${basePath()}/datafeeds/${datafeedId}?force=true`, - method: 'DELETE', - }); - }, - - startDatafeed({ datafeedId, start, end }: { datafeedId: string; start: number; end: number }) { - const body = JSON.stringify({ - ...(start !== undefined ? { start } : {}), - ...(end !== undefined ? { end } : {}), - }); - - return http({ - path: `${basePath()}/datafeeds/${datafeedId}/_start`, - method: 'POST', - body, - }); - }, - - stopDatafeed({ datafeedId }: { datafeedId: string }) { - return http({ - path: `${basePath()}/datafeeds/${datafeedId}/_stop`, - method: 'POST', - }); - }, - - datafeedPreview({ datafeedId }: { datafeedId: string }) { - return http({ - path: `${basePath()}/datafeeds/${datafeedId}/_preview`, - method: 'GET', - }); - }, - - validateDetector({ detector }: { detector: Detector }) { - const body = JSON.stringify(detector); - return http({ - path: `${basePath()}/anomaly_detectors/_validate/detector`, - method: 'POST', - body, - }); - }, - - forecast({ jobId, duration }: { jobId: string; duration?: string }) { - const body = JSON.stringify({ - ...(duration !== undefined ? { duration } : {}), - }); - - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}/_forecast`, - method: 'POST', - body, - }); - }, - - overallBuckets({ - jobId, - topN, - bucketSpan, - start, - end, - }: { - jobId: string; - topN: string; - bucketSpan: string; - start: number; - end: number; - }) { - const body = JSON.stringify({ topN, bucketSpan, start, end }); - return http({ - path: `${basePath()}/anomaly_detectors/${jobId}/results/overall_buckets`, - method: 'POST', - body, - }); - }, - - hasPrivileges(obj: any) { - const body = JSON.stringify(obj); - return http({ - path: `${basePath()}/_has_privileges`, - method: 'POST', - body, - }); - }, - - checkMlCapabilities() { - return http({ - path: `${basePath()}/ml_capabilities`, - method: 'GET', - }); - }, - - checkManageMLCapabilities() { - return http({ - path: `${basePath()}/ml_capabilities`, - method: 'GET', - }); - }, - - getNotificationSettings() { - return http({ - path: `${basePath()}/notification_settings`, - method: 'GET', - }); - }, - - getFieldCaps({ index, fields }: { index: string; fields: string[] }) { - const body = JSON.stringify({ - ...(index !== undefined ? { index } : {}), - ...(fields !== undefined ? { fields } : {}), - }); - - return http({ - path: `${basePath()}/indices/field_caps`, - method: 'POST', - body, - }); - }, - - recognizeIndex({ indexPatternTitle }: { indexPatternTitle: string }) { - return http({ - path: `${basePath()}/modules/recognize/${indexPatternTitle}`, - method: 'GET', - }); - }, - - listDataRecognizerModules() { - return http({ - path: `${basePath()}/modules/get_module`, - method: 'GET', - }); - }, - - getDataRecognizerModule({ moduleId }: { moduleId: string }) { - return http({ - path: `${basePath()}/modules/get_module/${moduleId}`, - method: 'GET', - }); - }, - - dataRecognizerModuleJobsExist({ moduleId }: { moduleId: string }) { - return http({ - path: `${basePath()}/modules/jobs_exist/${moduleId}`, - method: 'GET', - }); +/** + * Temp solution to allow {@link ml} service to use http from + * the dependency_cache. + */ +const proxyHttpStart = new Proxy(({} as unknown) as HttpStart, { + get(obj, prop: keyof HttpStart) { + try { + return getHttp()[prop]; + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } }, - - setupDataRecognizerConfig({ - moduleId, - prefix, - groups, - indexPatternName, - query, - useDedicatedIndex, - startDatafeed, - start, - end, - jobOverrides, - estimateModelMemory, - }: { - moduleId: string; - prefix?: string; - groups?: string[]; - indexPatternName?: string; - query?: any; - useDedicatedIndex?: boolean; - startDatafeed?: boolean; - start?: number; - end?: number; - jobOverrides?: Array>; - estimateModelMemory?: boolean; - }) { - const body = JSON.stringify({ +}); + +export type MlApiServices = ReturnType; + +export const ml = mlApiServicesProvider(new HttpService(proxyHttpStart)); + +export function mlApiServicesProvider(httpService: HttpService) { + const { http } = httpService; + return { + getJobs(obj?: { jobId?: string }) { + const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; + return httpService.http({ + path: `${basePath()}/anomaly_detectors${jobId}`, + }); + }, + + getJobStats(obj: { jobId?: string }) { + const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; + return httpService.http({ + path: `${basePath()}/anomaly_detectors${jobId}/_stats`, + }); + }, + + addJob({ jobId, job }: { jobId: string; job: Job }) { + const body = JSON.stringify(job); + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}`, + method: 'PUT', + body, + }); + }, + + openJob({ jobId }: { jobId: string }) { + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}/_open`, + method: 'POST', + }); + }, + + closeJob({ jobId }: { jobId: string }) { + return http({ + path: `${basePath()}/anomaly_detectors/${jobId}/_close`, + method: 'POST', + }); + }, + + forceCloseJob({ jobId }: { jobId: string }) { + return http({ + path: `${basePath()}/anomaly_detectors/${jobId}/_close?force=true`, + method: 'POST', + }); + }, + + deleteJob({ jobId }: { jobId: string }) { + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}`, + method: 'DELETE', + }); + }, + + forceDeleteJob({ jobId }: { jobId: string }) { + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}?force=true`, + method: 'DELETE', + }); + }, + + updateJob({ jobId, job }: { jobId: string; job: Job }) { + const body = JSON.stringify(job); + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}/_update`, + method: 'POST', + body, + }); + }, + + estimateBucketSpan(obj: BucketSpanEstimatorData) { + const body = JSON.stringify(obj); + return httpService.http({ + path: `${basePath()}/validate/estimate_bucket_span`, + method: 'POST', + body, + }); + }, + + validateJob(payload: { + job: Job; + duration: { + start?: number; + end?: number; + }; + fields?: any[]; + }) { + const body = JSON.stringify(payload); + return httpService.http({ + path: `${basePath()}/validate/job`, + method: 'POST', + body, + }); + }, + + validateCardinality$(job: CombinedJob): Observable { + const body = JSON.stringify(job); + return httpService.http$({ + path: `${basePath()}/validate/cardinality`, + method: 'POST', + body, + }); + }, + + getDatafeeds(obj: { datafeedId: string }) { + const datafeedId = obj && obj.datafeedId ? `/${obj.datafeedId}` : ''; + return httpService.http({ + path: `${basePath()}/datafeeds${datafeedId}`, + }); + }, + + getDatafeedStats(obj: { datafeedId: string }) { + const datafeedId = obj && obj.datafeedId ? `/${obj.datafeedId}` : ''; + return httpService.http({ + path: `${basePath()}/datafeeds${datafeedId}/_stats`, + }); + }, + + addDatafeed({ datafeedId, datafeedConfig }: { datafeedId: string; datafeedConfig: Datafeed }) { + const body = JSON.stringify(datafeedConfig); + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}`, + method: 'PUT', + body, + }); + }, + + updateDatafeed({ + datafeedId, + datafeedConfig, + }: { + datafeedId: string; + datafeedConfig: Datafeed; + }) { + const body = JSON.stringify(datafeedConfig); + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}/_update`, + method: 'POST', + body, + }); + }, + + deleteDatafeed({ datafeedId }: { datafeedId: string }) { + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}`, + method: 'DELETE', + }); + }, + + forceDeleteDatafeed({ datafeedId }: { datafeedId: string }) { + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}?force=true`, + method: 'DELETE', + }); + }, + + startDatafeed({ datafeedId, start, end }: { datafeedId: string; start: number; end: number }) { + const body = JSON.stringify({ + ...(start !== undefined ? { start } : {}), + ...(end !== undefined ? { end } : {}), + }); + + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}/_start`, + method: 'POST', + body, + }); + }, + + stopDatafeed({ datafeedId }: { datafeedId: string }) { + return http({ + path: `${basePath()}/datafeeds/${datafeedId}/_stop`, + method: 'POST', + }); + }, + + forceStopDatafeed({ datafeedId }: { datafeedId: string }) { + return http({ + path: `${basePath()}/datafeeds/${datafeedId}/_stop?force=true`, + method: 'POST', + }); + }, + + datafeedPreview({ datafeedId }: { datafeedId: string }) { + return httpService.http({ + path: `${basePath()}/datafeeds/${datafeedId}/_preview`, + method: 'GET', + }); + }, + + validateDetector({ detector }: { detector: Detector }) { + const body = JSON.stringify(detector); + return httpService.http({ + path: `${basePath()}/anomaly_detectors/_validate/detector`, + method: 'POST', + body, + }); + }, + + forecast({ jobId, duration }: { jobId: string; duration?: string }) { + const body = JSON.stringify({ + ...(duration !== undefined ? { duration } : {}), + }); + + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}/_forecast`, + method: 'POST', + body, + }); + }, + + overallBuckets({ + jobId, + topN, + bucketSpan, + start, + end, + }: { + jobId: string; + topN: string; + bucketSpan: string; + start: number; + end: number; + }) { + const body = JSON.stringify({ topN, bucketSpan, start, end }); + return httpService.http({ + path: `${basePath()}/anomaly_detectors/${jobId}/results/overall_buckets`, + method: 'POST', + body, + }); + }, + + hasPrivileges(obj: any) { + const body = JSON.stringify(obj); + return httpService.http({ + path: `${basePath()}/_has_privileges`, + method: 'POST', + body, + }); + }, + + checkMlCapabilities() { + return httpService.http({ + path: `${basePath()}/ml_capabilities`, + method: 'GET', + }); + }, + + checkManageMLCapabilities() { + return httpService.http({ + path: `${basePath()}/ml_capabilities`, + method: 'GET', + }); + }, + + getNotificationSettings() { + return httpService.http({ + path: `${basePath()}/notification_settings`, + method: 'GET', + }); + }, + + getFieldCaps({ index, fields }: { index: string; fields: string[] }) { + const body = JSON.stringify({ + ...(index !== undefined ? { index } : {}), + ...(fields !== undefined ? { fields } : {}), + }); + + return httpService.http({ + path: `${basePath()}/indices/field_caps`, + method: 'POST', + body, + }); + }, + + recognizeIndex({ indexPatternTitle }: { indexPatternTitle: string }) { + return httpService.http({ + path: `${basePath()}/modules/recognize/${indexPatternTitle}`, + method: 'GET', + }); + }, + + listDataRecognizerModules() { + return httpService.http({ + path: `${basePath()}/modules/get_module`, + method: 'GET', + }); + }, + + getDataRecognizerModule({ moduleId }: { moduleId: string }) { + return httpService.http({ + path: `${basePath()}/modules/get_module/${moduleId}`, + method: 'GET', + }); + }, + + dataRecognizerModuleJobsExist({ moduleId }: { moduleId: string }) { + return httpService.http({ + path: `${basePath()}/modules/jobs_exist/${moduleId}`, + method: 'GET', + }); + }, + + setupDataRecognizerConfig({ + moduleId, prefix, groups, indexPatternName, @@ -398,37 +423,41 @@ export const ml = { end, jobOverrides, estimateModelMemory, - }); - - return http({ - path: `${basePath()}/modules/setup/${moduleId}`, - method: 'POST', - body, - }); - }, - - getVisualizerFieldStats({ - indexPatternTitle, - query, - timeFieldName, - earliest, - latest, - samplerShardSize, - interval, - fields, - maxExamples, - }: { - indexPatternTitle: string; - query: any; - timeFieldName?: string; - earliest?: number; - latest?: number; - samplerShardSize?: number; - interval?: string; - fields?: FieldRequestConfig[]; - maxExamples?: number; - }) { - const body = JSON.stringify({ + }: { + moduleId: string; + prefix?: string; + groups?: string[]; + indexPatternName?: string; + query?: any; + useDedicatedIndex?: boolean; + startDatafeed?: boolean; + start?: number; + end?: number; + jobOverrides?: Array>; + estimateModelMemory?: boolean; + }) { + const body = JSON.stringify({ + prefix, + groups, + indexPatternName, + query, + useDedicatedIndex, + startDatafeed, + start, + end, + jobOverrides, + estimateModelMemory, + }); + + return httpService.http({ + path: `${basePath()}/modules/setup/${moduleId}`, + method: 'POST', + body, + }); + }, + + getVisualizerFieldStats({ + indexPatternTitle, query, timeFieldName, earliest, @@ -437,35 +466,37 @@ export const ml = { interval, fields, maxExamples, - }); - - return http({ - path: `${basePath()}/data_visualizer/get_field_stats/${indexPatternTitle}`, - method: 'POST', - body, - }); - }, - - getVisualizerOverallStats({ - indexPatternTitle, - query, - timeFieldName, - earliest, - latest, - samplerShardSize, - aggregatableFields, - nonAggregatableFields, - }: { - indexPatternTitle: string; - query: any; - timeFieldName?: string; - earliest?: number; - latest?: number; - samplerShardSize?: number; - aggregatableFields: string[]; - nonAggregatableFields: string[]; - }) { - const body = JSON.stringify({ + }: { + indexPatternTitle: string; + query: any; + timeFieldName?: string; + earliest?: number; + latest?: number; + samplerShardSize?: number; + interval?: string; + fields?: FieldRequestConfig[]; + maxExamples?: number; + }) { + const body = JSON.stringify({ + query, + timeFieldName, + earliest, + latest, + samplerShardSize, + interval, + fields, + maxExamples, + }); + + return httpService.http({ + path: `${basePath()}/data_visualizer/get_field_stats/${indexPatternTitle}`, + method: 'POST', + body, + }); + }, + + getVisualizerOverallStats({ + indexPatternTitle, query, timeFieldName, earliest, @@ -473,177 +504,230 @@ export const ml = { samplerShardSize, aggregatableFields, nonAggregatableFields, - }); - - return http({ - path: `${basePath()}/data_visualizer/get_overall_stats/${indexPatternTitle}`, - method: 'POST', - body, - }); - }, - - /** - * Gets a list of calendars - * @param obj - * @returns {Promise} - */ - calendars(obj?: { calendarId?: CalendarId; calendarIds?: CalendarId[] }) { - const { calendarId, calendarIds } = obj || {}; - let calendarIdsPathComponent = ''; - if (calendarId) { - calendarIdsPathComponent = `/${calendarId}`; - } else if (calendarIds) { - calendarIdsPathComponent = `/${calendarIds.join(',')}`; - } - return http({ - path: `${basePath()}/calendars${calendarIdsPathComponent}`, - method: 'GET', - }); - }, - - addCalendar(obj: Calendar) { - const body = JSON.stringify(obj); - return http({ - path: `${basePath()}/calendars`, - method: 'PUT', - body, - }); - }, - - updateCalendar(obj: UpdateCalendar) { - const calendarId = obj && obj.calendarId ? `/${obj.calendarId}` : ''; - const body = JSON.stringify(obj); - return http({ - path: `${basePath()}/calendars${calendarId}`, - method: 'PUT', - body, - }); - }, - - deleteCalendar({ calendarId }: { calendarId?: string }) { - return http({ - path: `${basePath()}/calendars/${calendarId}`, - method: 'DELETE', - }); - }, - - mlNodeCount() { - return http<{ count: number }>({ - path: `${basePath()}/ml_node_count`, - method: 'GET', - }); - }, - - mlInfo() { - return http({ - path: `${basePath()}/info`, - method: 'GET', - }); - }, - - calculateModelMemoryLimit$({ - analysisConfig, - indexPattern, - query, - timeFieldName, - earliestMs, - latestMs, - }: { - analysisConfig: AnalysisConfig; - indexPattern: string; - query: any; - timeFieldName: string; - earliestMs: number; - latestMs: number; - }) { - const body = JSON.stringify({ + }: { + indexPatternTitle: string; + query: any; + timeFieldName?: string; + earliest?: number; + latest?: number; + samplerShardSize?: number; + aggregatableFields: string[]; + nonAggregatableFields: string[]; + }) { + const body = JSON.stringify({ + query, + timeFieldName, + earliest, + latest, + samplerShardSize, + aggregatableFields, + nonAggregatableFields, + }); + + return httpService.http({ + path: `${basePath()}/data_visualizer/get_overall_stats/${indexPatternTitle}`, + method: 'POST', + body, + }); + }, + + /** + * Gets a list of calendars + * @param obj + * @returns {Promise} + */ + calendars(obj?: { calendarId?: CalendarId; calendarIds?: CalendarId[] }) { + const { calendarId, calendarIds } = obj || {}; + let calendarIdsPathComponent = ''; + if (calendarId) { + calendarIdsPathComponent = `/${calendarId}`; + } else if (calendarIds) { + calendarIdsPathComponent = `/${calendarIds.join(',')}`; + } + return httpService.http({ + path: `${basePath()}/calendars${calendarIdsPathComponent}`, + method: 'GET', + }); + }, + + addCalendar(obj: Calendar) { + const body = JSON.stringify(obj); + return httpService.http({ + path: `${basePath()}/calendars`, + method: 'PUT', + body, + }); + }, + + updateCalendar(obj: UpdateCalendar) { + const calendarId = obj && obj.calendarId ? `/${obj.calendarId}` : ''; + const body = JSON.stringify(obj); + return httpService.http({ + path: `${basePath()}/calendars${calendarId}`, + method: 'PUT', + body, + }); + }, + + deleteCalendar({ calendarId }: { calendarId?: string }) { + return httpService.http({ + path: `${basePath()}/calendars/${calendarId}`, + method: 'DELETE', + }); + }, + + mlNodeCount() { + return httpService.http<{ count: number }>({ + path: `${basePath()}/ml_node_count`, + method: 'GET', + }); + }, + + mlInfo() { + return httpService.http({ + path: `${basePath()}/info`, + method: 'GET', + }); + }, + + calculateModelMemoryLimit$({ analysisConfig, indexPattern, query, timeFieldName, earliestMs, latestMs, - }); - - return http$<{ modelMemoryLimit: string }>({ - path: `${basePath()}/validate/calculate_model_memory_limit`, - method: 'POST', - body, - }); - }, - - getCardinalityOfFields({ - index, - fieldNames, - query, - timeFieldName, - earliestMs, - latestMs, - }: { - index: string; - fieldNames: string[]; - query: any; - timeFieldName: string; - earliestMs: number; - latestMs: number; - }) { - const body = JSON.stringify({ index, fieldNames, query, timeFieldName, earliestMs, latestMs }); - - return http({ - path: `${basePath()}/fields_service/field_cardinality`, - method: 'POST', - body, - }); - }, - - getTimeFieldRange({ - index, - timeFieldName, - query, - }: { - index: string; - timeFieldName?: string; - query: any; - }) { - const body = JSON.stringify({ index, timeFieldName, query }); - - return http({ - path: `${basePath()}/fields_service/time_field_range`, - method: 'POST', - body, - }); - }, - - esSearch(obj: any) { - const body = JSON.stringify(obj); - return http({ - path: `${basePath()}/es_search`, - method: 'POST', - body, - }); - }, - - esSearch$(obj: any) { - const body = JSON.stringify(obj); - return http$({ - path: `${basePath()}/es_search`, - method: 'POST', - body, - }); - }, - - getIndices() { - const tempBasePath = '/api'; - return http>({ - path: `${tempBasePath}/index_management/indices`, - method: 'GET', - }); - }, - - annotations, - dataFrameAnalytics, - filters, - results, - jobs, - fileDatavisualizer, -}; + }: { + analysisConfig: AnalysisConfig; + indexPattern: string; + query: any; + timeFieldName: string; + earliestMs: number; + latestMs: number; + }) { + const body = JSON.stringify({ + analysisConfig, + indexPattern, + query, + timeFieldName, + earliestMs, + latestMs, + }); + + return httpService.http$<{ modelMemoryLimit: string }>({ + path: `${basePath()}/validate/calculate_model_memory_limit`, + method: 'POST', + body, + }); + }, + + getCardinalityOfFields({ + index, + fieldNames, + query, + timeFieldName, + earliestMs, + latestMs, + }: { + index: string; + fieldNames: string[]; + query: any; + timeFieldName: string; + earliestMs: number; + latestMs: number; + }) { + const body = JSON.stringify({ + index, + fieldNames, + query, + timeFieldName, + earliestMs, + latestMs, + }); + + return httpService.http({ + path: `${basePath()}/fields_service/field_cardinality`, + method: 'POST', + body, + }); + }, + + getTimeFieldRange({ + index, + timeFieldName, + query, + }: { + index: string; + timeFieldName?: string; + query: any; + }) { + const body = JSON.stringify({ index, timeFieldName, query }); + + return httpService.http({ + path: `${basePath()}/fields_service/time_field_range`, + method: 'POST', + body, + }); + }, + + esSearch(obj: any) { + const body = JSON.stringify(obj); + return httpService.http({ + path: `${basePath()}/es_search`, + method: 'POST', + body, + }); + }, + + esSearch$(obj: any) { + const body = JSON.stringify(obj); + return httpService.http$({ + path: `${basePath()}/es_search`, + method: 'POST', + body, + }); + }, + + getIndices() { + const tempBasePath = '/api'; + return httpService.http>({ + path: `${tempBasePath}/index_management/indices`, + method: 'GET', + }); + }, + + getModelSnapshots(jobId: string, snapshotId?: string) { + return http({ + path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots${ + snapshotId !== undefined ? `/${snapshotId}` : '' + }`, + }); + }, + + updateModelSnapshot( + jobId: string, + snapshotId: string, + body: { description?: string; retain?: boolean } + ) { + return http({ + path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots/${snapshotId}/_update`, + method: 'POST', + body: JSON.stringify(body), + }); + }, + + deleteModelSnapshot(jobId: string, snapshotId: string) { + return http({ + path: `${basePath()}/anomaly_detectors/${jobId}/model_snapshots/${snapshotId}`, + method: 'DELETE', + }); + }, + + annotations, + dataFrameAnalytics, + filters, + results: resultsApiProvider(httpService), + jobs, + fileDatavisualizer, + }; +} diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index e2569f6217b34..6aa62da3f0768 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -8,7 +8,11 @@ import { http } from '../http_service'; import { basePath } from './index'; import { Dictionary } from '../../../../common/types/common'; -import { MlJobWithTimeRange, MlSummaryJobs } from '../../../../common/types/anomaly_detection_jobs'; +import { + MlJobWithTimeRange, + MlSummaryJobs, + CombinedJobWithStats, +} from '../../../../common/types/anomaly_detection_jobs'; import { JobMessage } from '../../../../common/types/audit_message'; import { AggFieldNamePair } from '../../../../common/types/fields'; import { ExistingJobsAndGroups } from '../job_service'; @@ -41,7 +45,7 @@ export const jobs = { jobs(jobIds: string[]) { const body = JSON.stringify({ jobIds }); - return http({ + return http({ path: `${basePath()}/jobs/jobs`, method: 'POST', body, @@ -95,6 +99,7 @@ export const jobs = { body, }); }, + closeJobs(jobIds: string[]) { const body = JSON.stringify({ jobIds }); return http({ @@ -104,6 +109,15 @@ export const jobs = { }); }, + forceStopAndCloseJob(jobId: string) { + const body = JSON.stringify({ jobId }); + return http<{ success: boolean }>({ + path: `${basePath()}/jobs/force_stop_and_close_job`, + method: 'POST', + body, + }); + }, + jobAuditMessages(jobId: string, from?: number) { const jobIdString = jobId !== undefined ? `/${jobId}` : ''; const query = from !== undefined ? { from } : {}; @@ -255,4 +269,19 @@ export const jobs = { body, }); }, + + revertModelSnapshot( + jobId: string, + snapshotId: string, + replay: boolean, + end?: number, + calendarEvents?: Array<{ start: number; end: number; description: string }> + ) { + const body = JSON.stringify({ jobId, snapshotId, replay, end, calendarEvents }); + return http<{ total: number; categories: Array<{ count?: number; category: Category }> }>({ + path: `${basePath()}/jobs/revert_model_snapshot`, + method: 'POST', + body, + }); + }, }; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts index 830e6fab4163a..521fd306847eb 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts @@ -5,14 +5,14 @@ */ // Service for obtaining data for the ML Results dashboards. -import { http, http$ } from '../http_service'; +import { HttpService } from '../http_service'; import { basePath } from './index'; import { JobId } from '../../../../common/types/anomaly_detection_jobs'; import { PartitionFieldsDefinition } from '../results_service/result_service_rx'; -export const results = { +export const resultsApiProvider = (httpService: HttpService) => ({ getAnomaliesTableData( jobIds: string[], criteriaFields: string[], @@ -40,7 +40,7 @@ export const results = { influencersFilterQuery, }); - return http$({ + return httpService.http$({ path: `${basePath()}/results/anomalies_table_data`, method: 'POST', body, @@ -53,7 +53,7 @@ export const results = { earliestMs, latestMs, }); - return http({ + return httpService.http({ path: `${basePath()}/results/max_anomaly_score`, method: 'POST', body, @@ -62,7 +62,7 @@ export const results = { getCategoryDefinition(jobId: string, categoryId: string) { const body = JSON.stringify({ jobId, categoryId }); - return http({ + return httpService.http({ path: `${basePath()}/results/category_definition`, method: 'POST', body, @@ -75,7 +75,7 @@ export const results = { categoryIds, maxExamples, }); - return http({ + return httpService.http({ path: `${basePath()}/results/category_examples`, method: 'POST', body, @@ -90,10 +90,10 @@ export const results = { latestMs: number ) { const body = JSON.stringify({ jobId, searchTerm, criteriaFields, earliestMs, latestMs }); - return http$({ + return httpService.http$({ path: `${basePath()}/results/partition_fields_values`, method: 'POST', body, }); }, -}; +}); diff --git a/x-pack/plugins/ml/public/application/services/results_service/index.ts b/x-pack/plugins/ml/public/application/services/results_service/index.ts index cc02248f4d5a9..6c508422e7063 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/index.ts @@ -4,47 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - getMetricData, - getModelPlotOutput, - getRecordsForCriteria, - getScheduledEventsByBucket, - fetchPartitionFieldsValues, -} from './result_service_rx'; -import { - getEventDistributionData, - getEventRateData, - getInfluencerValueMaxScoreByTime, - getOverallBucketScores, - getRecordInfluencers, - getRecordMaxScoreByTime, - getRecords, - getRecordsForDetector, - getRecordsForInfluencer, - getScoresByBucket, - getTopInfluencers, - getTopInfluencerValues, -} from './results_service'; - -export const mlResultsService = { - getScoresByBucket, - getScheduledEventsByBucket, - getTopInfluencers, - getTopInfluencerValues, - getOverallBucketScores, - getInfluencerValueMaxScoreByTime, - getRecordInfluencers, - getRecordsForInfluencer, - getRecordsForDetector, - getRecords, - getRecordsForCriteria, - getMetricData, - getEventRateData, - getEventDistributionData, - getModelPlotOutput, - getRecordMaxScoreByTime, - fetchPartitionFieldsValues, -}; +import { resultsServiceRxProvider } from './result_service_rx'; +import { resultsServiceProvider } from './results_service'; +import { ml, MlApiServices } from '../ml_api_service'; export type MlResultsService = typeof mlResultsService; @@ -57,3 +19,12 @@ export interface CriteriaField { fieldName: string; fieldValue: any; } + +export const mlResultsService = mlResultsServiceProvider(ml); + +export function mlResultsServiceProvider(mlApiServices: MlApiServices) { + return { + ...resultsServiceProvider(mlApiServices), + ...resultsServiceRxProvider(mlApiServices), + }; +} diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index a21d0caaedd33..1bcbd8dbcdd63 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -17,7 +17,7 @@ import _ from 'lodash'; import { Dictionary } from '../../../../common/types/common'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; import { JobId } from '../../../../common/types/anomaly_detection_jobs'; -import { ml } from '../ml_api_service'; +import { MlApiServices } from '../ml_api_service'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; import { CriteriaField } from './index'; @@ -46,524 +46,528 @@ export type PartitionFieldsDefinition = { [field in FieldTypes]: FieldDefinition; }; -export function getMetricData( - index: string, - entityFields: any[], - query: object | undefined, - metricFunction: string, // ES aggregation name - metricFieldName: string, - timeFieldName: string, - earliestMs: number, - latestMs: number, - interval: string -): Observable { - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, entity fields, - // plus any additional supplied query. - const shouldCriteria: object[] = []; - const mustCriteria: object[] = [ - { - range: { - [timeFieldName]: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - ...(query ? [query] : []), - ]; - - entityFields.forEach((entity) => { - if (entity.fieldValue.length !== 0) { - mustCriteria.push({ - term: { - [entity.fieldName]: entity.fieldValue, - }, - }); - } else { - // Add special handling for blank entity field values, checking for either - // an empty string or the field not existing. - shouldCriteria.push({ - bool: { - must: [ - { - term: { - [entity.fieldName]: '', - }, - }, - ], - }, - }); - shouldCriteria.push({ - bool: { - must_not: [ - { - exists: { field: entity.fieldName }, - }, - ], - }, - }); - } - }); - - const body: any = { - query: { - bool: { - must: mustCriteria, - }, - }, - size: 0, - _source: { - excludes: [], - }, - aggs: { - byTime: { - date_histogram: { - field: timeFieldName, - interval, - min_doc_count: 0, - }, - }, - }, - }; - - if (shouldCriteria.length > 0) { - body.query.bool.should = shouldCriteria; - body.query.bool.minimum_should_match = shouldCriteria.length / 2; - } - - if (metricFieldName !== undefined && metricFieldName !== '') { - body.aggs.byTime.aggs = {}; - - const metricAgg: any = { - [metricFunction]: { - field: metricFieldName, - }, - }; - - if (metricFunction === 'percentiles') { - metricAgg[metricFunction].percents = [ML_MEDIAN_PERCENTS]; - } - body.aggs.byTime.aggs.metric = metricAgg; - } - - return ml.esSearch$({ index, body }).pipe( - map((resp: any) => { - const obj: MetricData = { success: true, results: {} }; - const dataByTime = resp?.aggregations?.byTime?.buckets ?? []; - dataByTime.forEach((dataForTime: any) => { - if (metricFunction === 'count') { - obj.results[dataForTime.key] = dataForTime.doc_count; - } else { - const value = dataForTime?.metric?.value; - const values = dataForTime?.metric?.values; - if (dataForTime.doc_count === 0) { - obj.results[dataForTime.key] = null; - } else if (value !== undefined) { - obj.results[dataForTime.key] = value; - } else if (values !== undefined) { - // Percentiles agg currently returns NaN rather than null when none of the docs in the - // bucket contain the field used in the aggregation - // (see elasticsearch issue https://github.com/elastic/elasticsearch/issues/29066). - // Store as null, so values can be handled in the same manner downstream as other aggs - // (min, mean, max) which return null. - const medianValues = values[ML_MEDIAN_PERCENTS]; - obj.results[dataForTime.key] = !isNaN(medianValues) ? medianValues : null; - } else { - obj.results[dataForTime.key] = null; - } - } - }); - - return obj; - }) - ); -} - export interface ModelPlotOutput extends ResultResponse { results: Record; } -export function getModelPlotOutput( - jobId: string, - detectorIndex: number, - criteriaFields: any[], - earliestMs: number, - latestMs: number, - interval: string, - aggType?: { min: any; max: any } -): Observable { - const obj: ModelPlotOutput = { - success: true, - results: {}, - }; +export interface RecordsForCriteria extends ResultResponse { + records: any[]; +} - // if an aggType object has been passed in, use it. - // otherwise default to min and max aggs for the upper and lower bounds - const modelAggs = - aggType === undefined - ? { max: 'max', min: 'min' } - : { - max: aggType.max, - min: aggType.min, - }; +export interface ScheduledEventsByBucket extends ResultResponse { + events: Record; +} - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the job ID and time range. - const mustCriteria: object[] = [ - { - term: { job_id: jobId }, - }, - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - ]; - - // Add in term queries for each of the specified criteria. - _.each(criteriaFields, (criteria) => { - mustCriteria.push({ - term: { - [criteria.fieldName]: criteria.fieldValue, - }, - }); - }); - - // Add criteria for the detector index. Results from jobs created before 6.1 will not - // contain a detector_index field, so use a should criteria with a 'not exists' check. - const shouldCriteria = [ - { - term: { detector_index: detectorIndex }, - }, - { - bool: { - must_not: [ - { - exists: { field: 'detector_index' }, +export function resultsServiceRxProvider(mlApiServices: MlApiServices) { + return { + getMetricData( + index: string, + entityFields: any[], + query: object | undefined, + metricFunction: string, // ES aggregation name + metricFieldName: string, + timeFieldName: string, + earliestMs: number, + latestMs: number, + interval: string + ): Observable { + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, entity fields, + // plus any additional supplied query. + const shouldCriteria: object[] = []; + const mustCriteria: object[] = [ + { + range: { + [timeFieldName]: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, }, - ], - }, - }, - ]; + }, + ...(query ? [query] : []), + ]; + + entityFields.forEach((entity) => { + if (entity.fieldValue.length !== 0) { + mustCriteria.push({ + term: { + [entity.fieldName]: entity.fieldValue, + }, + }); + } else { + // Add special handling for blank entity field values, checking for either + // an empty string or the field not existing. + shouldCriteria.push({ + bool: { + must: [ + { + term: { + [entity.fieldName]: '', + }, + }, + ], + }, + }); + shouldCriteria.push({ + bool: { + must_not: [ + { + exists: { field: entity.fieldName }, + }, + ], + }, + }); + } + }); - return ml - .esSearch$({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { + const body: any = { query: { bool: { - filter: [ - { - query_string: { - query: 'result_type:model_plot', - analyze_wildcard: true, - }, - }, - { - bool: { - must: mustCriteria, - should: shouldCriteria, - minimum_should_match: 1, - }, - }, - ], + must: mustCriteria, }, }, + size: 0, + _source: { + excludes: [], + }, aggs: { - times: { + byTime: { date_histogram: { - field: 'timestamp', + field: timeFieldName, interval, min_doc_count: 0, }, - aggs: { - actual: { - avg: { - field: 'actual', - }, - }, - modelUpper: { - [modelAggs.max]: { - field: 'model_upper', - }, - }, - modelLower: { - [modelAggs.min]: { - field: 'model_lower', - }, - }, - }, }, }, - }, - }) - .pipe( - map((resp) => { - const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); - _.each(aggregationsByTime, (dataForTime: any) => { - const time = dataForTime.key; - const modelUpper: number | undefined = _.get(dataForTime, ['modelUpper', 'value']); - const modelLower: number | undefined = _.get(dataForTime, ['modelLower', 'value']); - const actual = _.get(dataForTime, ['actual', 'value']); - - obj.results[time] = { - actual, - modelUpper: - modelUpper === undefined || isFinite(modelUpper) === false ? null : modelUpper, - modelLower: - modelLower === undefined || isFinite(modelLower) === false ? null : modelLower, - }; - }); + }; - return obj; - }) - ); -} + if (shouldCriteria.length > 0) { + body.query.bool.should = shouldCriteria; + body.query.bool.minimum_should_match = shouldCriteria.length / 2; + } -export interface RecordsForCriteria extends ResultResponse { - records: any[]; -} + if (metricFieldName !== undefined && metricFieldName !== '') { + body.aggs.byTime.aggs = {}; -// Queries Elasticsearch to obtain the record level results matching the given criteria, -// for the specified job(s), time range, and record score threshold. -// criteriaFields parameter must be an array, with each object in the array having 'fieldName' -// 'fieldValue' properties. -// Pass an empty array or ['*'] to search over all job IDs. -export function getRecordsForCriteria( - jobIds: string[] | undefined, - criteriaFields: CriteriaField[], - threshold: any, - earliestMs: number, - latestMs: number, - maxResults: number | undefined -): Observable { - const obj: RecordsForCriteria = { success: true, records: [] }; - - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, record score, plus any specified job IDs. - const boolCriteria: any[] = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - { - range: { - record_score: { - gte: threshold, - }, - }, - }, - ]; + const metricAgg: any = { + [metricFunction]: { + field: metricFieldName, + }, + }; - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; + if (metricFunction === 'percentiles') { + metricAgg[metricFunction].percents = [ML_MEDIAN_PERCENTS]; + } + body.aggs.byTime.aggs.metric = metricAgg; } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - // Add in term queries for each of the specified criteria. - _.each(criteriaFields, (criteria) => { - boolCriteria.push({ - term: { - [criteria.fieldName]: criteria.fieldValue, - }, - }); - }); - - return ml - .esSearch$({ - index: ML_RESULTS_INDEX_PATTERN, - rest_total_hits_as_int: true, - size: maxResults !== undefined ? maxResults : 100, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:record', - analyze_wildcard: false, - }, - }, - { - bool: { - must: boolCriteria, - }, - }, - ], - }, - }, - sort: [{ record_score: { order: 'desc' } }], - }, - }) - .pipe( - map((resp) => { - if (resp.hits.total !== 0) { - _.each(resp.hits.hits, (hit: any) => { - obj.records.push(hit._source); + + return mlApiServices.esSearch$({ index, body }).pipe( + map((resp: any) => { + const obj: MetricData = { success: true, results: {} }; + const dataByTime = resp?.aggregations?.byTime?.buckets ?? []; + dataByTime.forEach((dataForTime: any) => { + if (metricFunction === 'count') { + obj.results[dataForTime.key] = dataForTime.doc_count; + } else { + const value = dataForTime?.metric?.value; + const values = dataForTime?.metric?.values; + if (dataForTime.doc_count === 0) { + obj.results[dataForTime.key] = null; + } else if (value !== undefined) { + obj.results[dataForTime.key] = value; + } else if (values !== undefined) { + // Percentiles agg currently returns NaN rather than null when none of the docs in the + // bucket contain the field used in the aggregation + // (see elasticsearch issue https://github.com/elastic/elasticsearch/issues/29066). + // Store as null, so values can be handled in the same manner downstream as other aggs + // (min, mean, max) which return null. + const medianValues = values[ML_MEDIAN_PERCENTS]; + obj.results[dataForTime.key] = !isNaN(medianValues) ? medianValues : null; + } else { + obj.results[dataForTime.key] = null; + } + } }); - } - return obj; - }) - ); -} -export interface ScheduledEventsByBucket extends ResultResponse { - events: Record; -} + return obj; + }) + ); + }, -// Obtains a list of scheduled events by job ID and time. -// Pass an empty array or ['*'] to search over all job IDs. -// Returned response contains a events property, which will only -// contains keys for jobs which have scheduled events for the specified time range. -export function getScheduledEventsByBucket( - jobIds: string[] | undefined, - earliestMs: number, - latestMs: number, - interval: string, - maxJobs: number, - maxEvents: number -): Observable { - const obj: ScheduledEventsByBucket = { - success: true, - events: {}, - }; + getModelPlotOutput( + jobId: string, + detectorIndex: number, + criteriaFields: any[], + earliestMs: number, + latestMs: number, + interval: string, + aggType?: { min: any; max: any } + ): Observable { + const obj: ModelPlotOutput = { + success: true, + results: {}, + }; + + // if an aggType object has been passed in, use it. + // otherwise default to min and max aggs for the upper and lower bounds + const modelAggs = + aggType === undefined + ? { max: 'max', min: 'min' } + : { + max: aggType.max, + min: aggType.min, + }; + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the job ID and time range. + const mustCriteria: object[] = [ + { + term: { job_id: jobId }, + }, + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, + }, + ]; - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the time range plus any specified job IDs. - const boolCriteria: any[] = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', + // Add in term queries for each of the specified criteria. + _.each(criteriaFields, (criteria) => { + mustCriteria.push({ + term: { + [criteria.fieldName]: criteria.fieldValue, + }, + }); + }); + + // Add criteria for the detector index. Results from jobs created before 6.1 will not + // contain a detector_index field, so use a should criteria with a 'not exists' check. + const shouldCriteria = [ + { + term: { detector_index: detectorIndex }, }, - }, - }, - { - exists: { field: 'scheduled_events' }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - jobIdFilterStr += `${i > 0 ? ' OR ' : ''}job_id:${jobId}`; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - return ml - .esSearch$({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { + { bool: { - filter: [ + must_not: [ { - query_string: { - query: 'result_type:bucket', - analyze_wildcard: false, - }, - }, - { - bool: { - must: boolCriteria, - }, + exists: { field: 'detector_index' }, }, ], }, }, - aggs: { - jobs: { - terms: { - field: 'job_id', - min_doc_count: 1, - size: maxJobs, + ]; + + return mlApiServices + .esSearch$({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:model_plot', + analyze_wildcard: true, + }, + }, + { + bool: { + must: mustCriteria, + should: shouldCriteria, + minimum_should_match: 1, + }, + }, + ], + }, }, aggs: { times: { date_histogram: { field: 'timestamp', interval, - min_doc_count: 1, + min_doc_count: 0, }, aggs: { - events: { - terms: { - field: 'scheduled_events', - size: maxEvents, + actual: { + avg: { + field: 'actual', + }, + }, + modelUpper: { + [modelAggs.max]: { + field: 'model_upper', + }, + }, + modelLower: { + [modelAggs.min]: { + field: 'model_lower', }, }, }, }, }, }, + }) + .pipe( + map((resp) => { + const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); + _.each(aggregationsByTime, (dataForTime: any) => { + const time = dataForTime.key; + const modelUpper: number | undefined = _.get(dataForTime, ['modelUpper', 'value']); + const modelLower: number | undefined = _.get(dataForTime, ['modelLower', 'value']); + const actual = _.get(dataForTime, ['actual', 'value']); + + obj.results[time] = { + actual, + modelUpper: + modelUpper === undefined || isFinite(modelUpper) === false ? null : modelUpper, + modelLower: + modelLower === undefined || isFinite(modelLower) === false ? null : modelLower, + }; + }); + + return obj; + }) + ); + }, + + // Queries Elasticsearch to obtain the record level results matching the given criteria, + // for the specified job(s), time range, and record score threshold. + // criteriaFields parameter must be an array, with each object in the array having 'fieldName' + // 'fieldValue' properties. + // Pass an empty array or ['*'] to search over all job IDs. + getRecordsForCriteria( + jobIds: string[] | undefined, + criteriaFields: CriteriaField[], + threshold: any, + earliestMs: number, + latestMs: number, + maxResults: number | undefined + ): Observable { + const obj: RecordsForCriteria = { success: true, records: [] }; + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, record score, plus any specified job IDs. + const boolCriteria: any[] = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, }, - }, - }) - .pipe( - map((resp) => { - const dataByJobId = _.get(resp, ['aggregations', 'jobs', 'buckets'], []); - _.each(dataByJobId, (dataForJob: any) => { - const jobId: string = dataForJob.key; - const resultsForTime: Record = {}; - const dataByTime = _.get(dataForJob, ['times', 'buckets'], []); - _.each(dataByTime, (dataForTime: any) => { - const time: string = dataForTime.key; - const events: object[] = _.get(dataForTime, ['events', 'buckets']); - resultsForTime[time] = _.map(events, 'key'); - }); - obj.events[jobId] = resultsForTime; + { + range: { + record_score: { + gte: threshold, + }, + }, + }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, }); + } - return obj; - }) - ); -} + // Add in term queries for each of the specified criteria. + _.each(criteriaFields, (criteria) => { + boolCriteria.push({ + term: { + [criteria.fieldName]: criteria.fieldValue, + }, + }); + }); -export function fetchPartitionFieldsValues( - jobId: JobId, - searchTerm: Dictionary, - criteriaFields: Array<{ fieldName: string; fieldValue: any }>, - earliestMs: number, - latestMs: number -) { - return ml.results.fetchPartitionFieldsValues( - jobId, - searchTerm, - criteriaFields, - earliestMs, - latestMs - ); + return mlApiServices + .esSearch$({ + index: ML_RESULTS_INDEX_PATTERN, + rest_total_hits_as_int: true, + size: maxResults !== undefined ? maxResults : 100, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:record', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], + }, + }, + sort: [{ record_score: { order: 'desc' } }], + }, + }) + .pipe( + map((resp) => { + if (resp.hits.total !== 0) { + _.each(resp.hits.hits, (hit: any) => { + obj.records.push(hit._source); + }); + } + return obj; + }) + ); + }, + + // Obtains a list of scheduled events by job ID and time. + // Pass an empty array or ['*'] to search over all job IDs. + // Returned response contains a events property, which will only + // contains keys for jobs which have scheduled events for the specified time range. + getScheduledEventsByBucket( + jobIds: string[] | undefined, + earliestMs: number, + latestMs: number, + interval: string, + maxJobs: number, + maxEvents: number + ): Observable { + const obj: ScheduledEventsByBucket = { + success: true, + events: {}, + }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria: any[] = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, + }, + { + exists: { field: 'scheduled_events' }, + }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + jobIdFilterStr += `${i > 0 ? ' OR ' : ''}job_id:${jobId}`; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, + }); + } + + return mlApiServices + .esSearch$({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:bucket', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], + }, + }, + aggs: { + jobs: { + terms: { + field: 'job_id', + min_doc_count: 1, + size: maxJobs, + }, + aggs: { + times: { + date_histogram: { + field: 'timestamp', + interval, + min_doc_count: 1, + }, + aggs: { + events: { + terms: { + field: 'scheduled_events', + size: maxEvents, + }, + }, + }, + }, + }, + }, + }, + }, + }) + .pipe( + map((resp) => { + const dataByJobId = _.get(resp, ['aggregations', 'jobs', 'buckets'], []); + _.each(dataByJobId, (dataForJob: any) => { + const jobId: string = dataForJob.key; + const resultsForTime: Record = {}; + const dataByTime = _.get(dataForJob, ['times', 'buckets'], []); + _.each(dataByTime, (dataForTime: any) => { + const time: string = dataForTime.key; + const events: object[] = _.get(dataForTime, ['events', 'buckets']); + resultsForTime[time] = _.map(events, 'key'); + }); + obj.events[jobId] = resultsForTime; + }); + + return obj; + }) + ); + }, + + fetchPartitionFieldsValues( + jobId: JobId, + searchTerm: Dictionary, + criteriaFields: Array<{ fieldName: string; fieldValue: any }>, + earliestMs: number, + latestMs: number + ) { + return mlApiServices.results.fetchPartitionFieldsValues( + jobId, + searchTerm, + criteriaFields, + earliestMs, + latestMs + ); + }, + }; } diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index 4af08994432bd..1b2c01ab73fce 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -4,43 +4,49 @@ * you may not use this file except in compliance with the Elastic License. */ -export function getScoresByBucket( - jobIds: string[], - earliestMs: number, - latestMs: number, - interval: string | number, - maxResults: number -): Promise; -export function getTopInfluencers(): Promise; -export function getTopInfluencerValues(): Promise; -export function getOverallBucketScores( - jobIds: any, - topN: any, - earliestMs: any, - latestMs: any, - interval?: any -): Promise; -export function getInfluencerValueMaxScoreByTime( - jobIds: string[], - influencerFieldName: string, - influencerFieldValues: string[], - earliestMs: number, - latestMs: number, - interval: string, - maxResults: number, - influencersFilterQuery: any -): Promise; -export function getRecordInfluencers(): Promise; -export function getRecordsForInfluencer(): Promise; -export function getRecordsForDetector(): Promise; -export function getRecords(): Promise; -export function getEventRateData( - index: string, - query: any, - timeFieldName: string, - earliestMs: number, - latestMs: number, - interval: string | number -): Promise; -export function getEventDistributionData(): Promise; -export function getRecordMaxScoreByTime(): Promise; +import { MlApiServices } from '../ml_api_service'; + +export function resultsServiceProvider( + mlApiServices: MlApiServices +): { + getScoresByBucket( + jobIds: string[], + earliestMs: number, + latestMs: number, + interval: string | number, + maxResults: number + ): Promise; + getTopInfluencers(): Promise; + getTopInfluencerValues(): Promise; + getOverallBucketScores( + jobIds: any, + topN: any, + earliestMs: any, + latestMs: any, + interval?: any + ): Promise; + getInfluencerValueMaxScoreByTime( + jobIds: string[], + influencerFieldName: string, + influencerFieldValues: string[], + earliestMs: number, + latestMs: number, + interval: string, + maxResults: number, + influencersFilterQuery: any + ): Promise; + getRecordInfluencers(): Promise; + getRecordsForInfluencer(): Promise; + getRecordsForDetector(): Promise; + getRecords(): Promise; + getEventRateData( + index: string, + query: any, + timeFieldName: string, + earliestMs: number, + latestMs: number, + interval: string | number + ): Promise; + getEventDistributionData(): Promise; + getRecordMaxScoreByTime(): Promise; +}; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index 4fccc4d789370..9e3fed189b6f4 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -4,1322 +4,1331 @@ * you may not use this file except in compliance with the Elastic License. */ -// Service for carrying out Elasticsearch queries to obtain data for the -// Ml Results dashboards. import _ from 'lodash'; -// import d3 from 'd3'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; import { escapeForElasticsearchQuery } from '../../util/string_utils'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; -import { ml } from '../ml_api_service'; - -// Obtains the maximum bucket anomaly scores by job ID and time. -// Pass an empty array or ['*'] to search over all job IDs. -// Returned response contains a results property, with a key for job -// which has results for the specified time range. -export function getScoresByBucket(jobIds, earliestMs, latestMs, interval, maxResults) { - return new Promise((resolve, reject) => { - const obj = { - success: true, - results: {}, - }; - - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the time range plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; - } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:bucket', - analyze_wildcard: false, - }, - }, - { - bool: { - must: boolCriteria, - }, +/** + * Service for carrying out Elasticsearch queries to obtain data for the Ml Results dashboards. + */ +export function resultsServiceProvider(mlApiServices) { + const SAMPLER_TOP_TERMS_SHARD_SIZE = 20000; + const ENTITY_AGGREGATION_SIZE = 10; + const AGGREGATION_MIN_DOC_COUNT = 1; + const CARDINALITY_PRECISION_THRESHOLD = 100; + + return { + // Obtains the maximum bucket anomaly scores by job ID and time. + // Pass an empty array or ['*'] to search over all job IDs. + // Returned response contains a results property, with a key for job + // which has results for the specified time range. + getScoresByBucket(jobIds, earliestMs, latestMs, interval, maxResults) { + return new Promise((resolve, reject) => { + const obj = { + success: true, + results: {}, + }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, - ], + }, }, - }, - aggs: { - jobId: { - terms: { - field: 'job_id', - size: maxResults !== undefined ? maxResults : 5, - order: { - anomalyScore: 'desc', - }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, }, - aggs: { - anomalyScore: { - max: { - field: 'anomaly_score', + }); + } + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:bucket', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, }, - byTime: { - date_histogram: { - field: 'timestamp', - interval: interval, - min_doc_count: 1, - extended_bounds: { - min: earliestMs, - max: latestMs, + aggs: { + jobId: { + terms: { + field: 'job_id', + size: maxResults !== undefined ? maxResults : 5, + order: { + anomalyScore: 'desc', + }, }, - }, - aggs: { - anomalyScore: { - max: { - field: 'anomaly_score', + aggs: { + anomalyScore: { + max: { + field: 'anomaly_score', + }, + }, + byTime: { + date_histogram: { + field: 'timestamp', + interval: interval, + min_doc_count: 1, + extended_bounds: { + min: earliestMs, + max: latestMs, + }, + }, + aggs: { + anomalyScore: { + max: { + field: 'anomaly_score', + }, + }, + }, }, }, }, }, }, + }) + .then((resp) => { + const dataByJobId = _.get(resp, ['aggregations', 'jobId', 'buckets'], []); + _.each(dataByJobId, (dataForJob) => { + const jobId = dataForJob.key; + + const resultsForTime = {}; + + const dataByTime = _.get(dataForJob, ['byTime', 'buckets'], []); + _.each(dataByTime, (dataForTime) => { + const value = _.get(dataForTime, ['anomalyScore', 'value']); + if (value !== undefined) { + const time = dataForTime.key; + resultsForTime[time] = _.get(dataForTime, ['anomalyScore', 'value']); + } + }); + obj.results[jobId] = resultsForTime; + }); + + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); + }); + }, + + // Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s). + // Pass an empty array or ['*'] to search over all job IDs. + // An optional array of influencers may be supplied, with each object in the array having 'fieldName' + // and 'fieldValue' properties, to limit data to the supplied list of influencers. + // Returned response contains an influencers property, with a key for each of the influencer field names, + // whose value is an array of objects containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. + getTopInfluencers( + jobIds, + earliestMs, + latestMs, + maxFieldValues = 10, + influencers = [], + influencersFilterQuery + ) { + return new Promise((resolve, reject) => { + const obj = { success: true, influencers: {} }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, }, - }, - }, - }) - .then((resp) => { - const dataByJobId = _.get(resp, ['aggregations', 'jobId', 'buckets'], []); - _.each(dataByJobId, (dataForJob) => { - const jobId = dataForJob.key; - - const resultsForTime = {}; - - const dataByTime = _.get(dataForJob, ['byTime', 'buckets'], []); - _.each(dataByTime, (dataForTime) => { - const value = _.get(dataForTime, ['anomalyScore', 'value']); - if (value !== undefined) { - const time = dataForTime.key; - resultsForTime[time] = _.get(dataForTime, ['anomalyScore', 'value']); + { + range: { + influencer_score: { + gt: 0, + }, + }, + }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; }); - obj.results[jobId] = resultsForTime; - }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, + }); + } - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} + if (influencersFilterQuery !== undefined) { + boolCriteria.push(influencersFilterQuery); + } -// Obtains the top influencers, by maximum influencer score, for the specified index, time range and job ID(s). -// Pass an empty array or ['*'] to search over all job IDs. -// An optional array of influencers may be supplied, with each object in the array having 'fieldName' -// and 'fieldValue' properties, to limit data to the supplied list of influencers. -// Returned response contains an influencers property, with a key for each of the influencer field names, -// whose value is an array of objects containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. -export function getTopInfluencers( - jobIds, - earliestMs, - latestMs, - maxFieldValues = 10, - influencers = [], - influencersFilterQuery -) { - return new Promise((resolve, reject) => { - const obj = { success: true, influencers: {} }; - - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the time range plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - { - range: { - influencer_score: { - gt: 0, - }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; + // Add a should query to filter for each of the specified influencers. + if (influencers.length > 0) { + boolCriteria.push({ + bool: { + should: influencers.map((influencer) => { + return { + bool: { + must: [ + { term: { influencer_field_name: influencer.fieldName } }, + { term: { influencer_field_value: influencer.fieldValue } }, + ], + }, + }; + }), + minimum_should_match: 1, + }, + }); } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - if (influencersFilterQuery !== undefined) { - boolCriteria.push(influencersFilterQuery); - } - - // Add a should query to filter for each of the specified influencers. - if (influencers.length > 0) { - boolCriteria.push({ - bool: { - should: influencers.map((influencer) => { - return { - bool: { - must: [ - { term: { influencer_field_name: influencer.fieldName } }, - { term: { influencer_field_value: influencer.fieldValue } }, - ], - }, - }; - }), - minimum_should_match: 1, - }, - }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:influencer', - analyze_wildcard: false, - }, - }, - { + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { bool: { - must: boolCriteria, - }, - }, - ], - }, - }, - aggs: { - influencerFieldNames: { - terms: { - field: 'influencer_field_name', - size: 5, - order: { - maxAnomalyScore: 'desc', - }, - }, - aggs: { - maxAnomalyScore: { - max: { - field: 'influencer_score', + filter: [ + { + query_string: { + query: 'result_type:influencer', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, }, - influencerFieldValues: { - terms: { - field: 'influencer_field_value', - size: maxFieldValues, - order: { - maxAnomalyScore: 'desc', - }, - }, - aggs: { - maxAnomalyScore: { - max: { - field: 'influencer_score', + aggs: { + influencerFieldNames: { + terms: { + field: 'influencer_field_name', + size: 5, + order: { + maxAnomalyScore: 'desc', }, }, - sumAnomalyScore: { - sum: { - field: 'influencer_score', + aggs: { + maxAnomalyScore: { + max: { + field: 'influencer_score', + }, + }, + influencerFieldValues: { + terms: { + field: 'influencer_field_value', + size: maxFieldValues, + order: { + maxAnomalyScore: 'desc', + }, + }, + aggs: { + maxAnomalyScore: { + max: { + field: 'influencer_score', + }, + }, + sumAnomalyScore: { + sum: { + field: 'influencer_score', + }, + }, + }, }, }, }, }, }, - }, - }, - }, - }) - .then((resp) => { - const fieldNameBuckets = _.get( - resp, - ['aggregations', 'influencerFieldNames', 'buckets'], - [] - ); - _.each(fieldNameBuckets, (nameBucket) => { - const fieldName = nameBucket.key; - const fieldValues = []; - - const fieldValueBuckets = _.get(nameBucket, ['influencerFieldValues', 'buckets'], []); - _.each(fieldValueBuckets, (valueBucket) => { - const fieldValueResult = { - influencerFieldValue: valueBucket.key, - maxAnomalyScore: valueBucket.maxAnomalyScore.value, - sumAnomalyScore: valueBucket.sumAnomalyScore.value, - }; - fieldValues.push(fieldValueResult); - }); - - obj.influencers[fieldName] = fieldValues; - }); - - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} + }) + .then((resp) => { + const fieldNameBuckets = _.get( + resp, + ['aggregations', 'influencerFieldNames', 'buckets'], + [] + ); + _.each(fieldNameBuckets, (nameBucket) => { + const fieldName = nameBucket.key; + const fieldValues = []; + + const fieldValueBuckets = _.get(nameBucket, ['influencerFieldValues', 'buckets'], []); + _.each(fieldValueBuckets, (valueBucket) => { + const fieldValueResult = { + influencerFieldValue: valueBucket.key, + maxAnomalyScore: valueBucket.maxAnomalyScore.value, + sumAnomalyScore: valueBucket.sumAnomalyScore.value, + }; + fieldValues.push(fieldValueResult); + }); + + obj.influencers[fieldName] = fieldValues; + }); -// Obtains the top influencer field values, by maximum anomaly score, for a -// particular index, field name and job ID(s). -// Pass an empty array or ['*'] to search over all job IDs. -// Returned response contains a results property, which is an array of objects -// containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. -export function getTopInfluencerValues( - jobIds, - influencerFieldName, - earliestMs, - latestMs, - maxResults -) { - return new Promise((resolve, reject) => { - const obj = { success: true, results: [] }; - - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the time range plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; - } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: `result_type:influencer AND influencer_field_name: ${escapeForElasticsearchQuery( - influencerFieldName - )}`, - analyze_wildcard: false, - }, + }, + + // Obtains the top influencer field values, by maximum anomaly score, for a + // particular index, field name and job ID(s). + // Pass an empty array or ['*'] to search over all job IDs. + // Returned response contains a results property, which is an array of objects + // containing influencerFieldValue, maxAnomalyScore and sumAnomalyScore keys. + getTopInfluencerValues(jobIds, influencerFieldName, earliestMs, latestMs, maxResults) { + return new Promise((resolve, reject) => { + const obj = { success: true, results: [] }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, - { - bool: { - must: boolCriteria, - }, - }, - ], + }, }, - }, - aggs: { - influencerFieldValues: { - terms: { - field: 'influencer_field_value', - size: maxResults !== undefined ? maxResults : 2, - order: { - maxAnomalyScore: 'desc', - }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, }, - aggs: { - maxAnomalyScore: { - max: { - field: 'influencer_score', + }); + } + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: `result_type:influencer AND influencer_field_name: ${escapeForElasticsearchQuery( + influencerFieldName + )}`, + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, }, - sumAnomalyScore: { - sum: { - field: 'influencer_score', + aggs: { + influencerFieldValues: { + terms: { + field: 'influencer_field_value', + size: maxResults !== undefined ? maxResults : 2, + order: { + maxAnomalyScore: 'desc', + }, + }, + aggs: { + maxAnomalyScore: { + max: { + field: 'influencer_score', + }, + }, + sumAnomalyScore: { + sum: { + field: 'influencer_score', + }, + }, + }, }, }, }, - }, - }, - }, - }) - .then((resp) => { - const buckets = _.get(resp, ['aggregations', 'influencerFieldValues', 'buckets'], []); - _.each(buckets, (bucket) => { - const result = { - influencerFieldValue: bucket.key, - maxAnomalyScore: bucket.maxAnomalyScore.value, - sumAnomalyScore: bucket.sumAnomalyScore.value, - }; - obj.results.push(result); - }); + }) + .then((resp) => { + const buckets = _.get(resp, ['aggregations', 'influencerFieldValues', 'buckets'], []); + _.each(buckets, (bucket) => { + const result = { + influencerFieldValue: bucket.key, + maxAnomalyScore: bucket.maxAnomalyScore.value, + sumAnomalyScore: bucket.sumAnomalyScore.value, + }; + obj.results.push(result); + }); - resolve(obj); - }) - .catch((resp) => { - reject(resp); + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); }); - }); -} - -// Obtains the overall bucket scores for the specified job ID(s). -// Pass ['*'] to search over all job IDs. -// Returned response contains a results property as an object of max score by time. -export function getOverallBucketScores(jobIds, topN, earliestMs, latestMs, interval) { - return new Promise((resolve, reject) => { - const obj = { success: true, results: {} }; - - ml.overallBuckets({ - jobId: jobIds, - topN: topN, - bucketSpan: interval, - start: earliestMs, - end: latestMs, - }) - .then((resp) => { - const dataByTime = _.get(resp, ['overall_buckets'], []); - _.each(dataByTime, (dataForTime) => { - const value = _.get(dataForTime, ['overall_score']); - if (value !== undefined) { - obj.results[dataForTime.timestamp] = value; - } - }); + }, + + // Obtains the overall bucket scores for the specified job ID(s). + // Pass ['*'] to search over all job IDs. + // Returned response contains a results property as an object of max score by time. + getOverallBucketScores(jobIds, topN, earliestMs, latestMs, interval) { + return new Promise((resolve, reject) => { + const obj = { success: true, results: {} }; + + mlApiServices + .overallBuckets({ + jobId: jobIds, + topN: topN, + bucketSpan: interval, + start: earliestMs, + end: latestMs, + }) + .then((resp) => { + const dataByTime = _.get(resp, ['overall_buckets'], []); + _.each(dataByTime, (dataForTime) => { + const value = _.get(dataForTime, ['overall_score']); + if (value !== undefined) { + obj.results[dataForTime.timestamp] = value; + } + }); - resolve(obj); - }) - .catch((resp) => { - reject(resp); + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); }); - }); -} - -// Obtains the maximum score by influencer_field_value and by time for the specified job ID(s) -// (pass an empty array or ['*'] to search over all job IDs), and specified influencer field -// values (pass an empty array to search over all field values). -// Returned response contains a results property with influencer field values keyed -// against max score by time. -export function getInfluencerValueMaxScoreByTime( - jobIds, - influencerFieldName, - influencerFieldValues, - earliestMs, - latestMs, - interval, - maxResults, - influencersFilterQuery -) { - return new Promise((resolve, reject) => { - const obj = { success: true, results: {} }; - - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the time range plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', + }, + + // Obtains the maximum score by influencer_field_value and by time for the specified job ID(s) + // (pass an empty array or ['*'] to search over all job IDs), and specified influencer field + // values (pass an empty array to search over all field values). + // Returned response contains a results property with influencer field values keyed + // against max score by time. + getInfluencerValueMaxScoreByTime( + jobIds, + influencerFieldName, + influencerFieldValues, + earliestMs, + latestMs, + interval, + maxResults, + influencersFilterQuery + ) { + return new Promise((resolve, reject) => { + const obj = { success: true, results: {} }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, }, - }, - }, - { - range: { - influencer_score: { - gt: 0, + { + range: { + influencer_score: { + gt: 0, + }, + }, }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; - } - jobIdFilterStr += `job_id:${jobId}`; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } + ]; - if (influencersFilterQuery !== undefined) { - boolCriteria.push(influencersFilterQuery); - } + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += `job_id:${jobId}`; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, + }); + } - if (influencerFieldValues && influencerFieldValues.length > 0) { - let influencerFilterStr = ''; - _.each(influencerFieldValues, (value, i) => { - if (i > 0) { - influencerFilterStr += ' OR '; + if (influencersFilterQuery !== undefined) { + boolCriteria.push(influencersFilterQuery); } - if (value.trim().length > 0) { - influencerFilterStr += `influencer_field_value:${escapeForElasticsearchQuery(value)}`; - } else { - // Wrap whitespace influencer field values in quotes for the query_string query. - influencerFilterStr += `influencer_field_value:"${value}"`; + + if (influencerFieldValues && influencerFieldValues.length > 0) { + let influencerFilterStr = ''; + _.each(influencerFieldValues, (value, i) => { + if (i > 0) { + influencerFilterStr += ' OR '; + } + if (value.trim().length > 0) { + influencerFilterStr += `influencer_field_value:${escapeForElasticsearchQuery(value)}`; + } else { + // Wrap whitespace influencer field values in quotes for the query_string query. + influencerFilterStr += `influencer_field_value:"${value}"`; + } + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: influencerFilterStr, + }, + }); } - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: influencerFilterStr, - }, - }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: `result_type:influencer AND influencer_field_name: ${escapeForElasticsearchQuery( - influencerFieldName - )}`, - analyze_wildcard: false, + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: `result_type:influencer AND influencer_field_name: ${escapeForElasticsearchQuery( + influencerFieldName + )}`, + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], + }, + }, + aggs: { + influencerFieldValues: { + terms: { + field: 'influencer_field_value', + size: maxResults !== undefined ? maxResults : 10, + order: { + maxAnomalyScore: 'desc', + }, + }, + aggs: { + maxAnomalyScore: { + max: { + field: 'influencer_score', + }, + }, + byTime: { + date_histogram: { + field: 'timestamp', + interval, + min_doc_count: 1, + }, + aggs: { + maxAnomalyScore: { + max: { + field: 'influencer_score', + }, + }, + }, + }, + }, }, }, - { + }, + }) + .then((resp) => { + const fieldValueBuckets = _.get( + resp, + ['aggregations', 'influencerFieldValues', 'buckets'], + [] + ); + _.each(fieldValueBuckets, (valueBucket) => { + const fieldValue = valueBucket.key; + const fieldValues = {}; + + const timeBuckets = _.get(valueBucket, ['byTime', 'buckets'], []); + _.each(timeBuckets, (timeBucket) => { + const time = timeBucket.key; + const score = timeBucket.maxAnomalyScore.value; + fieldValues[time] = score; + }); + + obj.results[fieldValue] = fieldValues; + }); + + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); + }); + }, + + // Queries Elasticsearch to obtain record level results containing the influencers + // for the specified job(s), record score threshold, and time range. + // Pass an empty array or ['*'] to search over all job IDs. + // Returned response contains a records property, with each record containing + // only the fields job_id, detector_index, record_score and influencers. + getRecordInfluencers(jobIds, threshold, earliestMs, latestMs, maxResults) { + return new Promise((resolve, reject) => { + const obj = { success: true, records: [] }; + + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the existence of the nested influencers field, time range, + // record score, plus any specified job IDs. + const boolCriteria = [ + { + nested: { + path: 'influencers', + query: { bool: { - must: boolCriteria, + must: [ + { + exists: { field: 'influencers' }, + }, + ], }, }, - ], + }, }, - }, - aggs: { - influencerFieldValues: { - terms: { - field: 'influencer_field_value', - size: maxResults !== undefined ? maxResults : 10, - order: { - maxAnomalyScore: 'desc', + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, }, - aggs: { - maxAnomalyScore: { - max: { - field: 'influencer_score', - }, + }, + { + range: { + record_score: { + gte: threshold, }, - byTime: { - date_histogram: { - field: 'timestamp', - interval, - min_doc_count: 1, - }, - aggs: { - maxAnomalyScore: { - max: { - field: 'influencer_score', + }, + }, + ]; + + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, + }); + } + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: maxResults !== undefined ? maxResults : 100, + rest_total_hits_as_int: true, + body: { + _source: ['job_id', 'detector_index', 'influencers', 'record_score'], + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:record', + analyze_wildcard: false, + }, }, - }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, }, + sort: [{ record_score: { order: 'desc' } }], }, - }, - }, - }, - }) - .then((resp) => { - const fieldValueBuckets = _.get( - resp, - ['aggregations', 'influencerFieldValues', 'buckets'], - [] - ); - _.each(fieldValueBuckets, (valueBucket) => { - const fieldValue = valueBucket.key; - const fieldValues = {}; - - const timeBuckets = _.get(valueBucket, ['byTime', 'buckets'], []); - _.each(timeBuckets, (timeBucket) => { - const time = timeBucket.key; - const score = timeBucket.maxAnomalyScore.value; - fieldValues[time] = score; + }) + .then((resp) => { + if (resp.hits.total !== 0) { + _.each(resp.hits.hits, (hit) => { + obj.records.push(hit._source); + }); + } + resolve(obj); + }) + .catch((resp) => { + reject(resp); }); + }); + }, + + // Queries Elasticsearch to obtain the record level results containing the specified influencer(s), + // for the specified job(s), time range, and record score threshold. + // influencers parameter must be an array, with each object in the array having 'fieldName' + // 'fieldValue' properties. The influencer array uses 'should' for the nested bool query, + // so this returns record level results which have at least one of the influencers. + // Pass an empty array or ['*'] to search over all job IDs. + getRecordsForInfluencer( + jobIds, + influencers, + threshold, + earliestMs, + latestMs, + maxResults, + influencersFilterQuery + ) { + return new Promise((resolve, reject) => { + const obj = { success: true, records: [] }; + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, record score, plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, + }, + { + range: { + record_score: { + gte: threshold, + }, + }, + }, + ]; - obj.results[fieldValue] = fieldValues; - }); + if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { + let jobIdFilterStr = ''; + _.each(jobIds, (jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr, + }, + }); + } - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} + if (influencersFilterQuery !== undefined) { + boolCriteria.push(influencersFilterQuery); + } -// Queries Elasticsearch to obtain record level results containing the influencers -// for the specified job(s), record score threshold, and time range. -// Pass an empty array or ['*'] to search over all job IDs. -// Returned response contains a records property, with each record containing -// only the fields job_id, detector_index, record_score and influencers. -export function getRecordInfluencers(jobIds, threshold, earliestMs, latestMs, maxResults) { - return new Promise((resolve, reject) => { - const obj = { success: true, records: [] }; - - // Build the criteria to use in the bool filter part of the request. - // Adds criteria for the existence of the nested influencers field, time range, - // record score, plus any specified job IDs. - const boolCriteria = [ - { - nested: { - path: 'influencers', - query: { + // Add a nested query to filter for each of the specified influencers. + if (influencers.length > 0) { + boolCriteria.push({ bool: { - must: [ - { - exists: { field: 'influencers' }, + should: influencers.map((influencer) => { + return { + nested: { + path: 'influencers', + query: { + bool: { + must: [ + { + match: { + 'influencers.influencer_field_name': influencer.fieldName, + }, + }, + { + match: { + 'influencers.influencer_field_values': influencer.fieldValue, + }, + }, + ], + }, + }, + }, + }; + }), + minimum_should_match: 1, + }, + }); + } + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: maxResults !== undefined ? maxResults : 100, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:record', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, - ], + }, + sort: [{ record_score: { order: 'desc' } }], + }, + }) + .then((resp) => { + if (resp.hits.total !== 0) { + _.each(resp.hits.hits, (hit) => { + obj.records.push(hit._source); + }); + } + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); + }); + }, + + // Queries Elasticsearch to obtain the record level results for the specified job and detector, + // time range, record score threshold, and whether to only return results containing influencers. + // An additional, optional influencer field name and value may also be provided. + getRecordsForDetector( + jobId, + detectorIndex, + checkForInfluencers, + influencerFieldName, + influencerFieldValue, + threshold, + earliestMs, + latestMs, + maxResults + ) { + return new Promise((resolve, reject) => { + const obj = { success: true, records: [] }; + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, record score, plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, }, }, - }, - }, - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', + { + term: { job_id: jobId }, }, - }, - }, - { - range: { - record_score: { - gte: threshold, + { + term: { detector_index: detectorIndex }, }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; - } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: maxResults !== undefined ? maxResults : 100, - rest_total_hits_as_int: true, - body: { - _source: ['job_id', 'detector_index', 'influencers', 'record_score'], - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:record', - analyze_wildcard: false, - }, + { + range: { + record_score: { + gte: threshold, }, - { + }, + }, + ]; + + // Add a nested query to filter for the specified influencer field name and value. + if (influencerFieldName && influencerFieldValue) { + boolCriteria.push({ + nested: { + path: 'influencers', + query: { bool: { - must: boolCriteria, + must: [ + { + match: { + 'influencers.influencer_field_name': influencerFieldName, + }, + }, + { + match: { + 'influencers.influencer_field_values': influencerFieldValue, + }, + }, + ], }, }, - ], - }, - }, - sort: [{ record_score: { order: 'desc' } }], - }, - }) - .then((resp) => { - if (resp.hits.total !== 0) { - _.each(resp.hits.hits, (hit) => { - obj.records.push(hit._source); + }, }); - } - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} - -// Queries Elasticsearch to obtain the record level results containing the specified influencer(s), -// for the specified job(s), time range, and record score threshold. -// influencers parameter must be an array, with each object in the array having 'fieldName' -// 'fieldValue' properties. The influencer array uses 'should' for the nested bool query, -// so this returns record level results which have at least one of the influencers. -// Pass an empty array or ['*'] to search over all job IDs. -export function getRecordsForInfluencer( - jobIds, - influencers, - threshold, - earliestMs, - latestMs, - maxResults, - influencersFilterQuery -) { - return new Promise((resolve, reject) => { - const obj = { success: true, records: [] }; - - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, record score, plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - { - range: { - record_score: { - gte: threshold, - }, - }, - }, - ]; - - if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { - let jobIdFilterStr = ''; - _.each(jobIds, (jobId, i) => { - if (i > 0) { - jobIdFilterStr += ' OR '; - } - jobIdFilterStr += 'job_id:'; - jobIdFilterStr += jobId; - }); - boolCriteria.push({ - query_string: { - analyze_wildcard: false, - query: jobIdFilterStr, - }, - }); - } - - if (influencersFilterQuery !== undefined) { - boolCriteria.push(influencersFilterQuery); - } - - // Add a nested query to filter for each of the specified influencers. - if (influencers.length > 0) { - boolCriteria.push({ - bool: { - should: influencers.map((influencer) => { - return { + } else { + if (checkForInfluencers === true) { + boolCriteria.push({ nested: { path: 'influencers', query: { bool: { must: [ { - match: { - 'influencers.influencer_field_name': influencer.fieldName, - }, - }, - { - match: { - 'influencers.influencer_field_values': influencer.fieldValue, - }, + exists: { field: 'influencers' }, }, ], }, }, }, - }; - }), - minimum_should_match: 1, - }, - }); - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: maxResults !== undefined ? maxResults : 100, - rest_total_hits_as_int: true, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:record', - analyze_wildcard: false, - }, - }, - { - bool: { - must: boolCriteria, - }, - }, - ], - }, - }, - sort: [{ record_score: { order: 'desc' } }], - }, - }) - .then((resp) => { - if (resp.hits.total !== 0) { - _.each(resp.hits.hits, (hit) => { - obj.records.push(hit._source); - }); + }); + } } - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} -// Queries Elasticsearch to obtain the record level results for the specified job and detector, -// time range, record score threshold, and whether to only return results containing influencers. -// An additional, optional influencer field name and value may also be provided. -export function getRecordsForDetector( - jobId, - detectorIndex, - checkForInfluencers, - influencerFieldName, - influencerFieldValue, - threshold, - earliestMs, - latestMs, - maxResults -) { - return new Promise((resolve, reject) => { - const obj = { success: true, records: [] }; - - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, record score, plus any specified job IDs. - const boolCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - { - term: { job_id: jobId }, - }, - { - term: { detector_index: detectorIndex }, - }, - { - range: { - record_score: { - gte: threshold, - }, - }, - }, - ]; - - // Add a nested query to filter for the specified influencer field name and value. - if (influencerFieldName && influencerFieldValue) { - boolCriteria.push({ - nested: { - path: 'influencers', - query: { - bool: { - must: [ - { - match: { - 'influencers.influencer_field_name': influencerFieldName, - }, - }, - { - match: { - 'influencers.influencer_field_values': influencerFieldValue, - }, + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: maxResults !== undefined ? maxResults : 100, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: [ + { + query_string: { + query: 'result_type:record', + analyze_wildcard: false, + }, + }, + { + bool: { + must: boolCriteria, + }, + }, + ], }, - ], + }, + sort: [{ record_score: { order: 'desc' } }], }, - }, - }, + }) + .then((resp) => { + if (resp.hits.total !== 0) { + _.each(resp.hits.hits, (hit) => { + obj.records.push(hit._source); + }); + } + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); }); - } else { - if (checkForInfluencers === true) { - boolCriteria.push({ - nested: { - path: 'influencers', - query: { - bool: { - must: [ - { - exists: { field: 'influencers' }, - }, - ], + }, + + // Queries Elasticsearch to obtain all the record level results for the specified job(s), time range, + // and record score threshold. + // Pass an empty array or ['*'] to search over all job IDs. + // Returned response contains a records property, which is an array of the matching results. + getRecords(jobIds, threshold, earliestMs, latestMs, maxResults) { + return this.getRecordsForInfluencer(jobIds, [], threshold, earliestMs, latestMs, maxResults); + }, + + // Queries Elasticsearch to obtain event rate data i.e. the count + // of documents over time. + // index can be a String, or String[], of index names to search. + // Extra query object can be supplied, or pass null if no additional query. + // Returned response contains a results property, which is an object + // of document counts against time (epoch millis). + getEventRateData(index, query, timeFieldName, earliestMs, latestMs, interval) { + return new Promise((resolve, reject) => { + const obj = { success: true, results: {} }; + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, entity fields, + // plus any additional supplied query. + const mustCriteria = [ + { + range: { + [timeFieldName]: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, }, }, - }); - } - } - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: maxResults !== undefined ? maxResults : 100, - rest_total_hits_as_int: true, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:record', - analyze_wildcard: false, + ]; + + if (query) { + mustCriteria.push(query); + } + + mlApiServices + .esSearch({ + index, + rest_total_hits_as_int: true, + size: 0, + body: { + query: { + bool: { + must: mustCriteria, }, }, - { - bool: { - must: boolCriteria, + _source: { + excludes: [], + }, + aggs: { + eventRate: { + date_histogram: { + field: timeFieldName, + interval: interval, + min_doc_count: 0, + extended_bounds: { + min: earliestMs, + max: latestMs, + }, + }, }, }, - ], - }, - }, - sort: [{ record_score: { order: 'desc' } }], - }, - }) - .then((resp) => { - if (resp.hits.total !== 0) { - _.each(resp.hits.hits, (hit) => { - obj.records.push(hit._source); + }, + }) + .then((resp) => { + const dataByTimeBucket = _.get(resp, ['aggregations', 'eventRate', 'buckets'], []); + _.each(dataByTimeBucket, (dataForTime) => { + const time = dataForTime.key; + obj.results[time] = dataForTime.doc_count; + }); + obj.total = resp.hits.total; + + resolve(obj); + }) + .catch((resp) => { + reject(resp); }); - } - resolve(obj); - }) - .catch((resp) => { - reject(resp); }); - }); -} - -// Queries Elasticsearch to obtain all the record level results for the specified job(s), time range, -// and record score threshold. -// Pass an empty array or ['*'] to search over all job IDs. -// Returned response contains a records property, which is an array of the matching results. -export function getRecords(jobIds, threshold, earliestMs, latestMs, maxResults) { - return this.getRecordsForInfluencer(jobIds, [], threshold, earliestMs, latestMs, maxResults); -} + }, -// Queries Elasticsearch to obtain event rate data i.e. the count -// of documents over time. -// index can be a String, or String[], of index names to search. -// Extra query object can be supplied, or pass null if no additional query. -// Returned response contains a results property, which is an object -// of document counts against time (epoch millis). -export function getEventRateData(index, query, timeFieldName, earliestMs, latestMs, interval) { - return new Promise((resolve, reject) => { - const obj = { success: true, results: {} }; - - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, entity fields, - // plus any additional supplied query. - const mustCriteria = [ - { - range: { - [timeFieldName]: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - ]; - - if (query) { - mustCriteria.push(query); - } + // Queries Elasticsearch to obtain event distribution i.e. the count + // of entities over time. + // index can be a String, or String[], of index names to search. + // Extra query object can be supplied, or pass null if no additional query. + // Returned response contains a results property, which is an object + // of document counts against time (epoch millis). - ml.esSearch({ + getEventDistributionData( index, - rest_total_hits_as_int: true, - size: 0, - body: { - query: { - bool: { - must: mustCriteria, - }, - }, - _source: { - excludes: [], - }, - aggs: { - eventRate: { - date_histogram: { - field: timeFieldName, - interval: interval, - min_doc_count: 0, - extended_bounds: { - min: earliestMs, - max: latestMs, - }, + splitField, + filterField = null, + query, + metricFunction, // ES aggregation name + metricFieldName, + timeFieldName, + earliestMs, + latestMs, + interval + ) { + return new Promise((resolve, reject) => { + if (splitField === undefined) { + return resolve([]); + } + + // Build the criteria to use in the bool filter part of the request. + // Add criteria for the time range, entity fields, + // plus any additional supplied query. + const mustCriteria = []; + + mustCriteria.push({ + range: { + [timeFieldName]: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, }, - }, - }, - }) - .then((resp) => { - const dataByTimeBucket = _.get(resp, ['aggregations', 'eventRate', 'buckets'], []); - _.each(dataByTimeBucket, (dataForTime) => { - const time = dataForTime.key; - obj.results[time] = dataForTime.doc_count; }); - obj.total = resp.hits.total; - resolve(obj); - }) - .catch((resp) => { - reject(resp); - }); - }); -} + if (query) { + mustCriteria.push(query); + } -// Queries Elasticsearch to obtain event distribution i.e. the count -// of entities over time. -// index can be a String, or String[], of index names to search. -// Extra query object can be supplied, or pass null if no additional query. -// Returned response contains a results property, which is an object -// of document counts against time (epoch millis). -const SAMPLER_TOP_TERMS_SHARD_SIZE = 20000; -const ENTITY_AGGREGATION_SIZE = 10; -const AGGREGATION_MIN_DOC_COUNT = 1; -const CARDINALITY_PRECISION_THRESHOLD = 100; -export function getEventDistributionData( - index, - splitField, - filterField = null, - query, - metricFunction, // ES aggregation name - metricFieldName, - timeFieldName, - earliestMs, - latestMs, - interval -) { - return new Promise((resolve, reject) => { - if (splitField === undefined) { - return resolve([]); - } - - // Build the criteria to use in the bool filter part of the request. - // Add criteria for the time range, entity fields, - // plus any additional supplied query. - const mustCriteria = []; - - mustCriteria.push({ - range: { - [timeFieldName]: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }); - - if (query) { - mustCriteria.push(query); - } - - if (filterField !== null) { - mustCriteria.push({ - term: { - [filterField.fieldName]: filterField.fieldValue, - }, - }); - } - - const body = { - query: { - // using function_score and random_score to get a random sample of documents. - // otherwise all documents would have the same score and the sampler aggregation - // would pick the first N documents instead of a random set. - function_score: { - query: { - bool: { - must: mustCriteria, + if (filterField !== null) { + mustCriteria.push({ + term: { + [filterField.fieldName]: filterField.fieldValue, }, - }, - functions: [ - { - random_score: { - // static seed to get same randomized results on every request - seed: 10, - field: '_seq_no', + }); + } + + const body = { + query: { + // using function_score and random_score to get a random sample of documents. + // otherwise all documents would have the same score and the sampler aggregation + // would pick the first N documents instead of a random set. + function_score: { + query: { + bool: { + must: mustCriteria, + }, }, + functions: [ + { + random_score: { + // static seed to get same randomized results on every request + seed: 10, + field: '_seq_no', + }, + }, + ], }, - ], - }, - }, - size: 0, - _source: { - excludes: [], - }, - aggs: { - sample: { - sampler: { - shard_size: SAMPLER_TOP_TERMS_SHARD_SIZE, + }, + size: 0, + _source: { + excludes: [], }, aggs: { - byTime: { - date_histogram: { - field: timeFieldName, - interval: interval, - min_doc_count: AGGREGATION_MIN_DOC_COUNT, + sample: { + sampler: { + shard_size: SAMPLER_TOP_TERMS_SHARD_SIZE, }, aggs: { - entities: { - terms: { - field: splitField.fieldName, - size: ENTITY_AGGREGATION_SIZE, + byTime: { + date_histogram: { + field: timeFieldName, + interval: interval, min_doc_count: AGGREGATION_MIN_DOC_COUNT, }, + aggs: { + entities: { + terms: { + field: splitField.fieldName, + size: ENTITY_AGGREGATION_SIZE, + min_doc_count: AGGREGATION_MIN_DOC_COUNT, + }, + }, + }, }, }, }, }, - }, - }, - }; - - if (metricFieldName !== undefined && metricFieldName !== '') { - body.aggs.sample.aggs.byTime.aggs.entities.aggs = {}; - - const metricAgg = { - [metricFunction]: { - field: metricFieldName, - }, - }; - - if (metricFunction === 'percentiles') { - metricAgg[metricFunction].percents = [ML_MEDIAN_PERCENTS]; - } - - if (metricFunction === 'cardinality') { - metricAgg[metricFunction].precision_threshold = CARDINALITY_PRECISION_THRESHOLD; - } - body.aggs.sample.aggs.byTime.aggs.entities.aggs.metric = metricAgg; - } - - ml.esSearch({ - index, - body, - rest_total_hits_as_int: true, - }) - .then((resp) => { - // Because of the sampling, results of metricFunctions which use sum or count - // can be significantly skewed. Taking into account totalHits we calculate a - // a factor to normalize results for these metricFunctions. - const totalHits = _.get(resp, ['hits', 'total'], 0); - const successfulShards = _.get(resp, ['_shards', 'successful'], 0); - - let normalizeFactor = 1; - if (totalHits > successfulShards * SAMPLER_TOP_TERMS_SHARD_SIZE) { - normalizeFactor = totalHits / (successfulShards * SAMPLER_TOP_TERMS_SHARD_SIZE); + }; + + if (metricFieldName !== undefined && metricFieldName !== '') { + body.aggs.sample.aggs.byTime.aggs.entities.aggs = {}; + + const metricAgg = { + [metricFunction]: { + field: metricFieldName, + }, + }; + + if (metricFunction === 'percentiles') { + metricAgg[metricFunction].percents = [ML_MEDIAN_PERCENTS]; + } + + if (metricFunction === 'cardinality') { + metricAgg[metricFunction].precision_threshold = CARDINALITY_PRECISION_THRESHOLD; + } + body.aggs.sample.aggs.byTime.aggs.entities.aggs.metric = metricAgg; } - const dataByTime = _.get(resp, ['aggregations', 'sample', 'byTime', 'buckets'], []); - const data = dataByTime.reduce((d, dataForTime) => { - const date = +dataForTime.key; - const entities = _.get(dataForTime, ['entities', 'buckets'], []); - entities.forEach((entity) => { - let value = metricFunction === 'count' ? entity.doc_count : entity.metric.value; - - if ( - metricFunction === 'count' || - metricFunction === 'cardinality' || - metricFunction === 'sum' - ) { - value = value * normalizeFactor; + mlApiServices + .esSearch({ + index, + body, + rest_total_hits_as_int: true, + }) + .then((resp) => { + // Because of the sampling, results of metricFunctions which use sum or count + // can be significantly skewed. Taking into account totalHits we calculate a + // a factor to normalize results for these metricFunctions. + const totalHits = _.get(resp, ['hits', 'total'], 0); + const successfulShards = _.get(resp, ['_shards', 'successful'], 0); + + let normalizeFactor = 1; + if (totalHits > successfulShards * SAMPLER_TOP_TERMS_SHARD_SIZE) { + normalizeFactor = totalHits / (successfulShards * SAMPLER_TOP_TERMS_SHARD_SIZE); } - d.push({ - date, - entity: entity.key, - value, - }); + const dataByTime = _.get(resp, ['aggregations', 'sample', 'byTime', 'buckets'], []); + const data = dataByTime.reduce((d, dataForTime) => { + const date = +dataForTime.key; + const entities = _.get(dataForTime, ['entities', 'buckets'], []); + entities.forEach((entity) => { + let value = metricFunction === 'count' ? entity.doc_count : entity.metric.value; + + if ( + metricFunction === 'count' || + metricFunction === 'cardinality' || + metricFunction === 'sum' + ) { + value = value * normalizeFactor; + } + + d.push({ + date, + entity: entity.key, + value, + }); + }); + return d; + }, []); + resolve(data); + }) + .catch((resp) => { + reject(resp); }); - return d; - }, []); - resolve(data); - }) - .catch((resp) => { - reject(resp); }); - }); -} - -// Queries Elasticsearch to obtain the max record score over time for the specified job, -// criteria, time range, and aggregation interval. -// criteriaFields parameter must be an array, with each object in the array having 'fieldName' -// 'fieldValue' properties. -export function getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, latestMs, interval) { - return new Promise((resolve, reject) => { - const obj = { - success: true, - results: {}, - }; - - // Build the criteria to use in the bool filter part of the request. - const mustCriteria = [ - { - range: { - timestamp: { - gte: earliestMs, - lte: latestMs, - format: 'epoch_millis', - }, - }, - }, - { term: { job_id: jobId } }, - ]; - - _.each(criteriaFields, (criteria) => { - mustCriteria.push({ - term: { - [criteria.fieldName]: criteria.fieldValue, - }, - }); - }); - - ml.esSearch({ - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - query_string: { - query: 'result_type:record', - analyze_wildcard: true, - }, + }, + + // Queries Elasticsearch to obtain the max record score over time for the specified job, + // criteria, time range, and aggregation interval. + // criteriaFields parameter must be an array, with each object in the array having 'fieldName' + // 'fieldValue' properties. + getRecordMaxScoreByTime(jobId, criteriaFields, earliestMs, latestMs, interval) { + return new Promise((resolve, reject) => { + const obj = { + success: true, + results: {}, + }; + + // Build the criteria to use in the bool filter part of the request. + const mustCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', }, - { + }, + }, + { term: { job_id: jobId } }, + ]; + + _.each(criteriaFields, (criteria) => { + mustCriteria.push({ + term: { + [criteria.fieldName]: criteria.fieldValue, + }, + }); + }); + + mlApiServices + .esSearch({ + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { bool: { - must: mustCriteria, + filter: [ + { + query_string: { + query: 'result_type:record', + analyze_wildcard: true, + }, + }, + { + bool: { + must: mustCriteria, + }, + }, + ], }, }, - ], - }, - }, - aggs: { - times: { - date_histogram: { - field: 'timestamp', - interval: interval, - min_doc_count: 1, - }, - aggs: { - recordScore: { - max: { - field: 'record_score', + aggs: { + times: { + date_histogram: { + field: 'timestamp', + interval: interval, + min_doc_count: 1, + }, + aggs: { + recordScore: { + max: { + field: 'record_score', + }, + }, + }, }, }, }, - }, - }, - }, - }) - .then((resp) => { - const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); - _.each(aggregationsByTime, (dataForTime) => { - const time = dataForTime.key; - obj.results[time] = { - score: _.get(dataForTime, ['recordScore', 'value']), - }; - }); + }) + .then((resp) => { + const aggregationsByTime = _.get(resp, ['aggregations', 'times', 'buckets'], []); + _.each(aggregationsByTime, (dataForTime) => { + const time = dataForTime.key; + obj.results[time] = { + score: _.get(dataForTime, ['recordScore', 'value']), + }; + }); - resolve(obj); - }) - .catch((resp) => { - reject(resp); + resolve(obj); + }) + .catch((resp) => { + reject(resp); + }); }); - }); + }, + }; } diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js index 815aa4810ebaa..9069e8078fca6 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.js @@ -12,8 +12,7 @@ import { EuiButton, EuiButtonEmpty, EuiInMemoryTable, EuiSpacer } from '@elastic import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - -export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; +import { TIME_FORMAT } from '../../../../../../common/constants/time_format'; function DeleteButton({ onClick, canDeleteCalendar }) { return ( diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/index.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/index.js index 0910a04051bf4..89bd0235df996 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/index.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/index.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EventsTable, TIME_FORMAT } from './events_table'; +export { EventsTable } from './events_table'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js index 8380fd36b458c..d80e248674a8f 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.js @@ -24,7 +24,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import moment from 'moment'; -import { TIME_FORMAT } from '../events_table'; +import { TIME_FORMAT } from '../../../../../../common/constants/time_format'; import { generateTempId } from '../utils'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx index b53b08e5f6146..b4b25db452bdb 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx @@ -7,6 +7,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; import { Subject } from 'rxjs'; import { Embeddable, @@ -25,12 +26,19 @@ import { RefreshInterval, TimeRange, } from '../../../../../../src/plugins/data/common'; +import { SwimlaneType } from '../../application/explorer/explorer_constants'; export const ANOMALY_SWIMLANE_EMBEDDABLE_TYPE = 'ml_anomaly_swimlane'; +export const getDefaultPanelTitle = (jobIds: JobId[]) => + i18n.translate('xpack.ml.swimlaneEmbeddable.title', { + defaultMessage: 'ML anomaly swimlane for {jobIds}', + values: { jobIds: jobIds.join(', ') }, + }); + export interface AnomalySwimlaneEmbeddableCustomInput { jobIds: JobId[]; - swimlaneType: string; + swimlaneType: SwimlaneType; viewBy?: string; limit?: number; @@ -43,9 +51,12 @@ export interface AnomalySwimlaneEmbeddableCustomInput { export type AnomalySwimlaneEmbeddableInput = EmbeddableInput & AnomalySwimlaneEmbeddableCustomInput; -export interface AnomalySwimlaneEmbeddableOutput extends EmbeddableOutput { +export type AnomalySwimlaneEmbeddableOutput = EmbeddableOutput & + AnomalySwimlaneEmbeddableCustomOutput; + +export interface AnomalySwimlaneEmbeddableCustomOutput { jobIds: JobId[]; - swimlaneType: string; + swimlaneType: SwimlaneType; viewBy?: string; limit?: number; } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts index e86d738d8b809..09091b21e49b6 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts @@ -23,8 +23,9 @@ import { MlStartDependencies } from '../../plugin'; import { HttpService } from '../../application/services/http_service'; import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service'; import { ExplorerService } from '../../application/services/explorer_service'; -import { mlResultsService } from '../../application/services/results_service'; +import { mlResultsServiceProvider } from '../../application/services/results_service'; import { resolveAnomalySwimlaneUserInput } from './anomaly_swimlane_setup_flyout'; +import { mlApiServicesProvider } from '../../application/services/ml_api_service'; export class AnomalySwimlaneEmbeddableFactory implements EmbeddableFactoryDefinition { @@ -64,8 +65,7 @@ export class AnomalySwimlaneEmbeddableFactory const explorerService = new ExplorerService( pluginsStart.data.query.timefilter.timefilter, coreStart.uiSettings, - // TODO mlResultsService to use DI - mlResultsService + mlResultsServiceProvider(mlApiServicesProvider(httpService)) ); return [coreStart, pluginsStart, { anomalyDetectorService, explorerService }]; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx index 00d47c0d897c7..4c93b9ef23239 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { SWIMLANE_TYPE } from '../../application/explorer/explorer_constants'; +import { SWIMLANE_TYPE, SwimlaneType } from '../../application/explorer/explorer_constants'; import { AnomalySwimlaneEmbeddableInput } from './anomaly_swimlane_embeddable'; export interface AnomalySwimlaneInitializerProps { @@ -31,7 +31,7 @@ export interface AnomalySwimlaneInitializerProps { >; onCreate: (swimlaneProps: { panelTitle: string; - swimlaneType: string; + swimlaneType: SwimlaneType; viewBy?: string; limit?: number; }) => void; @@ -51,8 +51,8 @@ export const AnomalySwimlaneInitializer: FC = ( initialInput, }) => { const [panelTitle, setPanelTitle] = useState(defaultTitle); - const [swimlaneType, setSwimlaneType] = useState( - (initialInput?.swimlaneType ?? SWIMLANE_TYPE.OVERALL) as SWIMLANE_TYPE + const [swimlaneType, setSwimlaneType] = useState( + initialInput?.swimlaneType ?? SWIMLANE_TYPE.OVERALL ); const [viewBySwimlaneFieldName, setViewBySwimlaneFieldName] = useState(initialInput?.viewBy); const [limit, setLimit] = useState(initialInput?.limit ?? 5); @@ -135,7 +135,7 @@ export const AnomalySwimlaneInitializer: FC = ( })} options={swimlaneTypeOptions} idSelected={swimlaneType} - onChange={(id) => setSwimlaneType(id as SWIMLANE_TYPE)} + onChange={(id) => setSwimlaneType(id as SwimlaneType)} /> diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx index 83f9833109bf4..54f50d2d3da32 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { IUiSettingsClient, OverlayStart } from 'kibana/public'; -import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { VIEW_BY_JOB_LABEL } from '../../application/explorer/explorer_constants'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; @@ -14,7 +13,10 @@ import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer'; import { JobSelectorFlyout } from '../../application/components/job_selector/job_selector_flyout'; import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service'; import { getInitialGroupsMap } from '../../application/components/job_selector/job_selector'; -import { AnomalySwimlaneEmbeddableInput } from './anomaly_swimlane_embeddable'; +import { + AnomalySwimlaneEmbeddableInput, + getDefaultPanelTitle, +} from './anomaly_swimlane_embeddable'; export async function resolveAnomalySwimlaneUserInput( { @@ -52,12 +54,7 @@ export async function resolveAnomalySwimlaneUserInput( reject(); }} onSelectionConfirmed={async ({ jobIds, groups }) => { - const title = - input?.title ?? - i18n.translate('xpack.ml.swimlaneEmbeddable.title', { - defaultMessage: 'ML anomaly swimlane for {jobIds}', - values: { jobIds: jobIds.join(', ') }, - }); + const title = input?.title ?? getDefaultPanelTitle(jobIds); const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx index e5d8584683c55..0bba9b59f7bf7 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/explorer_swimlane_container.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useState } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import { EuiCallOut, EuiFlexGroup, @@ -28,6 +28,7 @@ import { } from './anomaly_swimlane_embeddable'; import { MlTooltipComponent } from '../../application/components/chart_tooltip'; import { useSwimlaneInputResolver } from './swimlane_input_resolver'; +import { SwimlaneType } from '../../application/explorer/explorer_constants'; const RESIZE_THROTTLE_TIME_MS = 500; @@ -54,10 +55,13 @@ export const ExplorerSwimlaneContainer: FC = ({ chartWidth ); - const onResize = throttle((e: { width: number; height: number }) => { - const labelWidth = 200; - setChartWidth(e.width - labelWidth); - }, RESIZE_THROTTLE_TIME_MS); + const onResize = useCallback( + throttle((e: { width: number; height: number }) => { + const labelWidth = 200; + setChartWidth(e.width - labelWidth); + }, RESIZE_THROTTLE_TIME_MS), + [] + ); if (error) { return ( @@ -91,14 +95,14 @@ export const ExplorerSwimlaneContainer: FC = ({ {chartWidth > 0 && swimlaneData && swimlaneType ? ( - + {(tooltipService) => ( )} diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts index e704582d5d61a..3829bbce5e5c9 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.ts @@ -24,7 +24,7 @@ import { AnomalySwimlaneServices, } from './anomaly_swimlane_embeddable'; import { MlStartDependencies } from '../../plugin'; -import { SWIMLANE_TYPE } from '../../application/explorer/explorer_constants'; +import { SWIMLANE_TYPE, SwimlaneType } from '../../application/explorer/explorer_constants'; import { Filter } from '../../../../../../src/plugins/data/common/es_query/filters'; import { Query } from '../../../../../../src/plugins/data/common/query'; import { esKuery, UI_SETTINGS } from '../../../../../../src/plugins/data/public'; @@ -55,7 +55,7 @@ export function useSwimlaneInputResolver( const [{ uiSettings }, , { explorerService, anomalyDetectorService }] = services; const [swimlaneData, setSwimlaneData] = useState(); - const [swimlaneType, setSwimlaneType] = useState(); + const [swimlaneType, setSwimlaneType] = useState(); const [error, setError] = useState(); const chartWidth$ = useMemo(() => new Subject(), []); diff --git a/x-pack/plugins/ml/public/index.ts b/x-pack/plugins/ml/public/index.ts index a9ffb1a5bf579..5a956651c86d8 100755 --- a/x-pack/plugins/ml/public/index.ts +++ b/x-pack/plugins/ml/public/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializer } from 'kibana/public'; +import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import './index.scss'; import { MlPlugin, @@ -19,7 +19,7 @@ export const plugin: PluginInitializer< MlPluginStart, MlSetupDependencies, MlStartDependencies -> = () => new MlPlugin(); +> = (initializerContext: PluginInitializerContext) => new MlPlugin(initializerContext); export { MlPluginSetup, MlPluginStart }; export * from './shared'; diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index fe9f602bc3637..be2ebb3caa416 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -5,7 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Plugin, CoreStart, CoreSetup, AppMountParameters } from 'kibana/public'; +import { + Plugin, + CoreStart, + CoreSetup, + AppMountParameters, + PluginInitializerContext, +} from 'kibana/public'; import { ManagementSetup } from 'src/plugins/management/public'; import { SharePluginStart } from 'src/plugins/share/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; @@ -38,9 +44,13 @@ export interface MlSetupDependencies { home: HomePublicPluginSetup; embeddable: EmbeddableSetup; uiActions: UiActionsSetup; + kibanaVersion: string; + share: SharePluginStart; } export class MlPlugin implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + setup(core: CoreSetup, pluginsSetup: MlSetupDependencies) { core.application.register({ id: PLUGIN_ID, @@ -53,6 +63,7 @@ export class MlPlugin implements Plugin { category: DEFAULT_APP_CATEGORIES.kibana, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); + const kibanaVersion = this.initializerContext.env.packageInfo.version; const { renderApp } = await import('./application/app'); return renderApp( coreStart, @@ -67,6 +78,7 @@ export class MlPlugin implements Plugin { home: pluginsSetup.home, embeddable: pluginsSetup.embeddable, uiActions: pluginsSetup.uiActions, + kibanaVersion, }, { element: params.element, diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts index d5c7882a30d20..07159534e1e2c 100644 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts +++ b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts @@ -393,6 +393,17 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) ml.stopDatafeed = ca({ urls: [ + { + fmt: '/_ml/datafeeds/<%=datafeedId%>/_stop?force=<%=force%>', + req: { + datafeedId: { + type: 'string', + }, + force: { + type: 'boolean', + }, + }, + }, { fmt: '/_ml/datafeeds/<%=datafeedId%>/_stop', req: { @@ -823,4 +834,81 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) ], method: 'GET', }); + + ml.modelSnapshots = ca({ + urls: [ + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>', + req: { + jobId: { + type: 'string', + }, + snapshotId: { + type: 'string', + }, + }, + }, + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/model_snapshots', + req: { + jobId: { + type: 'string', + }, + }, + }, + ], + method: 'GET', + }); + + ml.updateModelSnapshot = ca({ + urls: [ + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>/_update', + req: { + jobId: { + type: 'string', + }, + snapshotId: { + type: 'string', + }, + }, + }, + ], + method: 'POST', + needBody: true, + }); + + ml.deleteModelSnapshot = ca({ + urls: [ + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>', + req: { + jobId: { + type: 'string', + }, + snapshotId: { + type: 'string', + }, + }, + }, + ], + method: 'DELETE', + }); + + ml.revertModelSnapshot = ca({ + urls: [ + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/model_snapshots/<%=snapshotId%>/_revert', + req: { + jobId: { + type: 'string', + }, + snapshotId: { + type: 'string', + }, + }, + }, + ], + method: 'POST', + }); }; diff --git a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts index acb1bed6a37c0..2eec704f1e784 100644 --- a/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts @@ -5,7 +5,7 @@ */ import { difference } from 'lodash'; -import { IScopedClusterClient } from 'kibana/server'; +import { APICaller } from 'kibana/server'; import { EventManager, CalendarEvent } from './event_manager'; interface BasicCalendar { @@ -23,16 +23,16 @@ export interface FormCalendar extends BasicCalendar { } export class CalendarManager { - private _client: IScopedClusterClient['callAsCurrentUser']; - private _eventManager: any; + private _callAsCurrentUser: APICaller; + private _eventManager: EventManager; - constructor(client: any) { - this._client = client; - this._eventManager = new EventManager(client); + constructor(callAsCurrentUser: APICaller) { + this._callAsCurrentUser = callAsCurrentUser; + this._eventManager = new EventManager(callAsCurrentUser); } async getCalendar(calendarId: string) { - const resp = await this._client('ml.calendars', { + const resp = await this._callAsCurrentUser('ml.calendars', { calendarId, }); @@ -43,7 +43,7 @@ export class CalendarManager { } async getAllCalendars() { - const calendarsResp = await this._client('ml.calendars'); + const calendarsResp = await this._callAsCurrentUser('ml.calendars'); const events: CalendarEvent[] = await this._eventManager.getAllEvents(); const calendars: Calendar[] = calendarsResp.calendars; @@ -74,7 +74,7 @@ export class CalendarManager { const events = calendar.events; delete calendar.calendarId; delete calendar.events; - await this._client('ml.addCalendar', { + await this._callAsCurrentUser('ml.addCalendar', { calendarId, body: calendar, }); @@ -109,7 +109,7 @@ export class CalendarManager { // add all new jobs if (jobsToAdd.length) { - await this._client('ml.addJobToCalendar', { + await this._callAsCurrentUser('ml.addJobToCalendar', { calendarId, jobId: jobsToAdd.join(','), }); @@ -117,7 +117,7 @@ export class CalendarManager { // remove all removed jobs if (jobsToRemove.length) { - await this._client('ml.removeJobFromCalendar', { + await this._callAsCurrentUser('ml.removeJobFromCalendar', { calendarId, jobId: jobsToRemove.join(','), }); @@ -131,7 +131,7 @@ export class CalendarManager { // remove all removed events await Promise.all( eventsToRemove.map(async (event) => { - await this._eventManager.deleteEvent(calendarId, event.event_id); + await this._eventManager.deleteEvent(calendarId, event.event_id!); }) ); @@ -140,6 +140,6 @@ export class CalendarManager { } async deleteCalendar(calendarId: string) { - return this._client('ml.deleteCalendar', { calendarId }); + return this._callAsCurrentUser('ml.deleteCalendar', { calendarId }); } } diff --git a/x-pack/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/plugins/ml/server/models/calendar/event_manager.ts index 41240e2695f6f..02da0718d83ae 100644 --- a/x-pack/plugins/ml/server/models/calendar/event_manager.ts +++ b/x-pack/plugins/ml/server/models/calendar/event_manager.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APICaller } from 'kibana/server'; import { GLOBAL_CALENDAR } from '../../../common/constants/calendars'; export interface CalendarEvent { @@ -15,13 +16,10 @@ export interface CalendarEvent { } export class EventManager { - private _client: any; - constructor(client: any) { - this._client = client; - } + constructor(private _callAsCurrentUser: APICaller) {} async getCalendarEvents(calendarId: string) { - const resp = await this._client('ml.events', { calendarId }); + const resp = await this._callAsCurrentUser('ml.events', { calendarId }); return resp.events; } @@ -29,7 +27,7 @@ export class EventManager { // jobId is optional async getAllEvents(jobId?: string) { const calendarId = GLOBAL_CALENDAR; - const resp = await this._client('ml.events', { + const resp = await this._callAsCurrentUser('ml.events', { calendarId, jobId, }); @@ -40,14 +38,14 @@ export class EventManager { async addEvents(calendarId: string, events: CalendarEvent[]) { const body = { events }; - return await this._client('ml.addEvent', { + return await this._callAsCurrentUser('ml.addEvent', { calendarId, body, }); } async deleteEvent(calendarId: string, eventId: string) { - return this._client('ml.deleteEvent', { calendarId, eventId }); + return this._callAsCurrentUser('ml.deleteEvent', { calendarId, eventId }); } isEqual(ev1: CalendarEvent, ev2: CalendarEvent) { diff --git a/x-pack/plugins/ml/server/models/calendar/index.ts b/x-pack/plugins/ml/server/models/calendar/index.ts index 2364c3ac73811..1a35f9f13368e 100644 --- a/x-pack/plugins/ml/server/models/calendar/index.ts +++ b/x-pack/plugins/ml/server/models/calendar/index.ts @@ -5,3 +5,4 @@ */ export { CalendarManager, Calendar, FormCalendar } from './calendar_manager'; +export { CalendarEvent } from './event_manager'; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json index 1737039093432..f671d3fa1dec0 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json @@ -1,6 +1,6 @@ { "title": "ML Auditbeat Docker: Process Occurrence - experimental (ECS)", - "visState": "{\"type\":\"vega\",\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega-lite/v2.json\\n mark: {type: \\\"point\\\"}\\n data: {\\n url: {\\n index: \\\"INDEX_PATTERN_NAME\\\"\\n body: {\\n size: 10000\\n query: {\\n bool: {\\n must: [\\n %dashboard_context-must_clause%\\n {\\n exists: {field: \\\"process.executable\\\"}\\n }\\n {\\n function_score: {\\n random_score: {seed: 10, field: \\\"_seq_no\\\"}\\n }\\n }\\n {\\n range: {\\n @timestamp: {\\n %timefilter%: true\\n }\\n }\\n }\\n ]\\n must_not: [\\n \\\"%dashboard_context-must_not_clause%\\\"\\n ]\\n }\\n }\\n script_fields: {\\n process_exe: {\\n script: {source: \\\"params['_source']['process']['executable']\\\"}\\n }\\n }\\n _source: [\\\"@timestamp\\\", \\\"process_exe\\\"]\\n }\\n }\\n format: {property: \\\"hits.hits\\\"}\\n }\\n transform: [\\n {calculate: \\\"toDate(datum._source['@timestamp'])\\\", as: \\\"time\\\"}\\n ]\\n encoding: {\\n x: {\\n field: time\\n type: temporal\\n axis: {labels: true, ticks: true, title: false},\\n timeUnit: utcyearmonthdatehoursminutes\\n }\\n y: {\\n field: fields.process_exe\\n type: ordinal\\n sort: {op: \\\"count\\\", order: \\\"descending\\\"}\\n axis: {labels: true, title: \\\"occurrence of process.executable\\\", ticks: false}\\n }\\n }\\n config: {\\n style: {\\n point: {filled: true}\\n }\\n }\\n}\"},\"aggs\":[]}", + "visState": "{\"type\":\"vega\",\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega-lite/v4.json\\n width: \\\"container\\\"\\n mark: {type: \\\"point\\\"}\\n data: {\\n url: {\\n index: \\\"INDEX_PATTERN_NAME\\\"\\n body: {\\n size: 10000\\n query: {\\n bool: {\\n must: [\\n %dashboard_context-must_clause%\\n {\\n exists: {field: \\\"process.executable\\\"}\\n }\\n {\\n function_score: {\\n random_score: {seed: 10, field: \\\"_seq_no\\\"}\\n }\\n }\\n {\\n range: {\\n @timestamp: {\\n %timefilter%: true\\n }\\n }\\n }\\n ]\\n must_not: [\\n \\\"%dashboard_context-must_not_clause%\\\"\\n ]\\n }\\n }\\n script_fields: {\\n process_exe: {\\n script: {source: \\\"params['_source']['process']['executable']\\\"}\\n }\\n }\\n _source: [\\\"@timestamp\\\", \\\"process_exe\\\"]\\n }\\n }\\n format: {property: \\\"hits.hits\\\"}\\n }\\n transform: [\\n {calculate: \\\"toDate(datum._source['@timestamp'])\\\", as: \\\"time\\\"}\\n ]\\n encoding: {\\n x: {\\n field: time\\n type: temporal\\n axis: {labels: true, ticks: true, title: false},\\n timeUnit: utcyearmonthdatehoursminutes\\n }\\n y: {\\n field: fields.process_exe\\n type: ordinal\\n sort: {op: \\\"count\\\", order: \\\"descending\\\"}\\n axis: {labels: true, title: \\\"occurrence of process.executable\\\", ticks: false}\\n }\\n }\\n config: {\\n style: {\\n point: {filled: true}\\n }\\n }\\n}\"},\"aggs\":[]}", "uiStateJSON": "{}", "description": "", "savedSearchId": "ml_auditbeat_docker_process_events_ecs", diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json index b172ac7ff21cc..0d28081818ac7 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json @@ -1,6 +1,6 @@ { "title": "ML Auditbeat Hosts: Process Occurrence - experimental (ECS)", - "visState": "{\"type\":\"vega\",\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega-lite/v2.json\\n mark: {type: \\\"point\\\"}\\n data: {\\n url: {\\n index: \\\"INDEX_PATTERN_NAME\\\"\\n body: {\\n size: 10000\\n query: {\\n bool: {\\n must: [\\n %dashboard_context-must_clause%\\n {\\n exists: {field: \\\"process.executable\\\"}\\n }\\n {\\n function_score: {\\n random_score: {seed: 10, field: \\\"_seq_no\\\"}\\n }\\n }\\n {\\n range: {\\n @timestamp: {\\n %timefilter%: true\\n }\\n }\\n }\\n ]\\n must_not: [\\n \\\"%dashboard_context-must_not_clause%\\\"\\n ]\\n }\\n }\\n script_fields: {\\n process_exe: {\\n script: {source: \\\"params['_source']['process']['executable']\\\"}\\n }\\n }\\n _source: [\\\"@timestamp\\\", \\\"process_exe\\\"]\\n }\\n }\\n format: {property: \\\"hits.hits\\\"}\\n }\\n transform: [\\n {calculate: \\\"toDate(datum._source['@timestamp'])\\\", as: \\\"time\\\"}\\n ]\\n encoding: {\\n x: {\\n field: time\\n type: temporal\\n axis: {labels: true, ticks: true, title: false},\\n timeUnit: utcyearmonthdatehoursminutes\\n }\\n y: {\\n field: fields.process_exe\\n type: ordinal\\n sort: {op: \\\"count\\\", order: \\\"descending\\\"}\\n axis: {labels: true, title: \\\"occurrence of process.executable\\\", ticks: false}\\n }\\n }\\n config: {\\n style: {\\n point: {filled: true}\\n }\\n }\\n}\"},\"aggs\":[]}", + "visState": "{\"type\":\"vega\",\"params\":{\"spec\":\"{\\n $schema: https://vega.github.io/schema/vega-lite/v4.json\\n width: \\\"container\\\"\\n mark: {type: \\\"point\\\"}\\n data: {\\n url: {\\n index: \\\"INDEX_PATTERN_NAME\\\"\\n body: {\\n size: 10000\\n query: {\\n bool: {\\n must: [\\n %dashboard_context-must_clause%\\n {\\n exists: {field: \\\"process.executable\\\"}\\n }\\n {\\n function_score: {\\n random_score: {seed: 10, field: \\\"_seq_no\\\"}\\n }\\n }\\n {\\n range: {\\n @timestamp: {\\n %timefilter%: true\\n }\\n }\\n }\\n ]\\n must_not: [\\n \\\"%dashboard_context-must_not_clause%\\\"\\n ]\\n }\\n }\\n script_fields: {\\n process_exe: {\\n script: {source: \\\"params['_source']['process']['executable']\\\"}\\n }\\n }\\n _source: [\\\"@timestamp\\\", \\\"process_exe\\\"]\\n }\\n }\\n format: {property: \\\"hits.hits\\\"}\\n }\\n transform: [\\n {calculate: \\\"toDate(datum._source['@timestamp'])\\\", as: \\\"time\\\"}\\n ]\\n encoding: {\\n x: {\\n field: time\\n type: temporal\\n axis: {labels: true, ticks: true, title: false},\\n timeUnit: utcyearmonthdatehoursminutes\\n }\\n y: {\\n field: fields.process_exe\\n type: ordinal\\n sort: {op: \\\"count\\\", order: \\\"descending\\\"}\\n axis: {labels: true, title: \\\"occurrence of process.executable\\\", ticks: false}\\n }\\n }\\n config: {\\n style: {\\n point: {filled: true}\\n }\\n }\\n}\"},\"aggs\":[]}", "uiStateJSON": "{}", "description": "", "savedSearchId": "ml_auditbeat_hosts_process_events_ecs", diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts index 4090a59c461da..f016898075918 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -27,7 +27,7 @@ interface Results { } export function datafeedsProvider(callAsCurrentUser: APICaller) { - async function forceStartDatafeeds(datafeedIds: string[], start: number, end: number) { + async function forceStartDatafeeds(datafeedIds: string[], start?: number, end?: number) { const jobIds = await getJobIdsByDatafeedId(); const doStartsCalled = datafeedIds.reduce((acc, cur) => { acc[cur] = false; @@ -96,7 +96,7 @@ export function datafeedsProvider(callAsCurrentUser: APICaller) { return opened; } - async function startDatafeed(datafeedId: string, start: number, end: number) { + async function startDatafeed(datafeedId: string, start?: number, end?: number) { return callAsCurrentUser('ml.startDatafeed', { datafeedId, start, end }); } diff --git a/x-pack/plugins/ml/server/models/job_service/index.ts b/x-pack/plugins/ml/server/models/job_service/index.ts index eb70a3ccecfc1..b6e87b98735ee 100644 --- a/x-pack/plugins/ml/server/models/job_service/index.ts +++ b/x-pack/plugins/ml/server/models/job_service/index.ts @@ -10,6 +10,7 @@ import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; import { newJobCapsProvider } from './new_job_caps'; import { newJobChartsProvider, topCategoriesProvider } from './new_job'; +import { modelSnapshotProvider } from './model_snapshots'; export function jobServiceProvider(callAsCurrentUser: APICaller) { return { @@ -19,5 +20,6 @@ export function jobServiceProvider(callAsCurrentUser: APICaller) { ...newJobCapsProvider(callAsCurrentUser), ...newJobChartsProvider(callAsCurrentUser), ...topCategoriesProvider(callAsCurrentUser), + ...modelSnapshotProvider(callAsCurrentUser), }; } diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 5503169f2d371..852264b3d0337 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { uniq } from 'lodash'; +import Boom from 'boom'; import { APICaller } from 'kibana/server'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; import { @@ -128,6 +129,23 @@ export function jobsProvider(callAsCurrentUser: APICaller) { return results; } + async function forceStopAndCloseJob(jobId: string) { + const datafeedIds = await getDatafeedIdsByJobId(); + const datafeedId = datafeedIds[jobId]; + if (datafeedId === undefined) { + throw Boom.notFound(`Cannot find datafeed for job ${jobId}`); + } + + const dfResult = await callAsCurrentUser('ml.stopDatafeed', { datafeedId, force: true }); + if (!dfResult || dfResult.stopped !== true) { + return { success: false }; + } + + await callAsCurrentUser('ml.closeJob', { jobId, force: true }); + + return { success: true }; + } + async function jobsSummary(jobIds: string[] = []) { const fullJobsList: CombinedJobWithStats[] = await createFullJobsList(); const fullJobsIds = fullJobsList.map((job) => job.job_id); @@ -472,6 +490,7 @@ export function jobsProvider(callAsCurrentUser: APICaller) { forceDeleteJob, deleteJobs, closeJobs, + forceStopAndCloseJob, jobsSummary, jobsWithTimerange, createFullJobsList, diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts new file mode 100644 index 0000000000000..4ffae17fc1c06 --- /dev/null +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { i18n } from '@kbn/i18n'; +import { APICaller } from 'kibana/server'; +import { ModelSnapshot } from '../../../common/types/anomaly_detection_jobs'; +import { datafeedsProvider, MlDatafeedsResponse } from './datafeeds'; +import { MlJobsResponse } from './jobs'; +import { FormCalendar, CalendarManager } from '../calendar'; + +export interface ModelSnapshotsResponse { + count: number; + model_snapshots: ModelSnapshot[]; +} +export interface RevertModelSnapshotResponse { + model: ModelSnapshot; +} + +export function modelSnapshotProvider(callAsCurrentUser: APICaller) { + const { forceStartDatafeeds, getDatafeedIdsByJobId } = datafeedsProvider(callAsCurrentUser); + + async function revertModelSnapshot( + jobId: string, + snapshotId: string, + replay: boolean, + end?: number, + deleteInterveningResults: boolean = true, + calendarEvents?: [{ start: number; end: number; description: string }] + ) { + let datafeedId = `datafeed-${jobId}`; + // ensure job exists + await callAsCurrentUser('ml.jobs', { jobId: [jobId] }); + + try { + // ensure the datafeed exists + // the datafeed is probably called datafeed- + await callAsCurrentUser('ml.datafeeds', { + datafeedId: [datafeedId], + }); + } catch (e) { + // if the datafeed isn't called datafeed- + // check all datafeeds to see if one exists that is matched to this job id + const datafeedIds = await getDatafeedIdsByJobId(); + datafeedId = datafeedIds[jobId]; + if (datafeedId === undefined) { + throw Boom.notFound(`Cannot find datafeed for job ${jobId}`); + } + } + + // ensure the snapshot exists + const snapshot = await callAsCurrentUser('ml.modelSnapshots', { + jobId, + snapshotId, + }); + + // apply the snapshot revert + const { model } = await callAsCurrentUser( + 'ml.revertModelSnapshot', + { + jobId, + snapshotId, + body: { + delete_intervening_results: deleteInterveningResults, + }, + } + ); + + // create calendar (if specified) and replay datafeed + if (replay && model.snapshot_id === snapshotId && snapshot.model_snapshots.length) { + // create calendar before starting restarting the datafeed + if (calendarEvents !== undefined && calendarEvents.length) { + const calendar: FormCalendar = { + calendarId: String(Date.now()), + job_ids: [jobId], + description: i18n.translate( + 'xpack.ml.models.jobService.revertModelSnapshot.autoCreatedCalendar.description', + { + defaultMessage: 'Auto created', + } + ), + events: calendarEvents.map((s) => ({ + description: s.description, + start_time: s.start, + end_time: s.end, + })), + }; + const cm = new CalendarManager(callAsCurrentUser); + await cm.newCalendar(calendar); + } + + forceStartDatafeeds([datafeedId], snapshot.model_snapshots[0].latest_record_time_stamp, end); + } + + return { success: true }; + } + + return { revertModelSnapshot }; +} diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index 63cd5498231af..0b544d4eca0ed 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -17,6 +17,8 @@ import { getCategoriesSchema, forecastAnomalyDetector, getBucketParamsSchema, + getModelSnapshotsSchema, + updateModelSnapshotSchema, } from './schemas/anomaly_detectors_schema'; /** @@ -526,7 +528,7 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { /** * @apiGroup AnomalyDetectors * - * @api {get} /api/ml/anomaly_detectors/:jobId/results/categories/:categoryId Get results category data by job id and category id + * @api {get} /api/ml/anomaly_detectors/:jobId/results/categories/:categoryId Get results category data by job ID and category ID * @apiName GetCategories * @apiDescription Returns the categories results for the specified job ID and category ID. * @@ -544,11 +546,148 @@ export function jobRoutes({ router, mlLicense }: RouteInitialization) { }, mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { - const options = { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.categories', { jobId: request.params.jobId, categoryId: request.params.categoryId, - }; - const results = await context.ml!.mlClient.callAsCurrentUser('ml.categories', options); + }); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup AnomalyDetectors + * + * @api {get} /api/ml/anomaly_detectors/:jobId/model_snapshots Get model snapshots by job ID + * @apiName GetModelSnapshots + * @apiDescription Returns the model snapshots for the specified job ID + * + * @apiSchema (params) getModelSnapshotsSchema + */ + router.get( + { + path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots', + validate: { + params: getModelSnapshotsSchema, + }, + options: { + tags: ['access:ml:canGetJobs'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.modelSnapshots', { + jobId: request.params.jobId, + }); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup AnomalyDetectors + * + * @api {get} /api/ml/anomaly_detectors/:jobId/model_snapshots/:snapshotId Get model snapshots by job ID and snapshot ID + * @apiName GetModelSnapshotsById + * @apiDescription Returns the model snapshots for the specified job ID and snapshot ID + * + * @apiSchema (params) getModelSnapshotsSchema + */ + router.get( + { + path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots/{snapshotId}', + validate: { + params: getModelSnapshotsSchema, + }, + options: { + tags: ['access:ml:canGetJobs'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.modelSnapshots', { + jobId: request.params.jobId, + snapshotId: request.params.snapshotId, + }); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup AnomalyDetectors + * + * @api {post} /api/ml/anomaly_detectors/:jobId/model_snapshots/:snapshotId/_update update model snapshot by snapshot ID + * @apiName UpdateModelSnapshotsById + * @apiDescription Updates the model snapshot for the specified snapshot ID + * + * @apiSchema (params) getModelSnapshotsSchema + * @apiSchema (body) updateModelSnapshotSchema + */ + router.post( + { + path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots/{snapshotId}/_update', + validate: { + params: getModelSnapshotsSchema, + body: updateModelSnapshotSchema, + }, + options: { + tags: ['access:ml:canCreateJob'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.updateModelSnapshot', { + jobId: request.params.jobId, + snapshotId: request.params.snapshotId, + body: request.body, + }); + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup AnomalyDetectors + * + * @api {delete} /api/ml/anomaly_detectors/:jobId/model_snapshots/:snapshotId Delete model snapshots by snapshot ID + * @apiName GetModelSnapshotsById + * @apiDescription Deletes the model snapshot for the specified snapshot ID + * + * @apiSchema (params) getModelSnapshotsSchema + */ + router.delete( + { + path: '/api/ml/anomaly_detectors/{jobId}/model_snapshots/{snapshotId}', + validate: { + params: getModelSnapshotsSchema, + }, + options: { + tags: ['access:ml:canCreateJob'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const results = await context.ml!.mlClient.callAsCurrentUser('ml.deleteModelSnapshot', { + jobId: request.params.jobId, + snapshotId: request.params.snapshotId, + }); return response.ok({ body: results, }); diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 05c44e1da9757..10d1c9952b540 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -17,8 +17,11 @@ import { lookBackProgressSchema, topCategoriesSchema, updateGroupsSchema, + revertModelSnapshotSchema, } from './schemas/job_service_schema'; +import { jobIdSchema } from './schemas/anomaly_detectors_schema'; + import { jobServiceProvider } from '../models/job_service'; import { categorizationExamplesProvider } from '../models/job_service/new_job'; @@ -162,6 +165,40 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { }) ); + /** + * @apiGroup JobService + * + * @api {post} /api/ml/jobs/force_stop_and_close_job Force stop and close job + * @apiName ForceStopAndCloseJob + * @apiDescription Force stops the datafeed and then force closes the anomaly detection job specified by job ID + * + * @apiSchema (body) jobIdSchema + */ + router.post( + { + path: '/api/ml/jobs/force_stop_and_close_job', + validate: { + body: jobIdSchema, + }, + options: { + tags: ['access:ml:canCloseJob', 'access:ml:canStartStopDatafeed'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const { forceStopAndCloseJob } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); + const { jobId } = request.body; + const resp = await forceStopAndCloseJob(jobId); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + /** * @apiGroup JobService * @@ -691,4 +728,52 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { } }) ); + + /** + * @apiGroup JobService + * + * @api {post} /api/ml/jobs/revert_model_snapshot Revert model snapshot + * @apiName RevertModelSnapshot + * @apiDescription Reverts a job to a specified snapshot. Also allows the job to replayed to a specified date and to auto create calendars to skip analysis of specified date ranges + * + * @apiSchema (body) revertModelSnapshotSchema + */ + router.post( + { + path: '/api/ml/jobs/revert_model_snapshot', + validate: { + body: revertModelSnapshotSchema, + }, + options: { + tags: ['access:ml:canCreateJob', 'access:ml:canStartStopDatafeed'], + }, + }, + mlLicense.fullLicenseAPIGuard(async (context, request, response) => { + try { + const { revertModelSnapshot } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); + const { + jobId, + snapshotId, + replay, + end, + deleteInterveningResults, + calendarEvents, + } = request.body; + const resp = await revertModelSnapshot( + jobId, + snapshotId, + replay, + end, + deleteInterveningResults, + calendarEvents + ); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); } diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 3a01a616a4f7c..16eaab20fe8cb 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -178,10 +178,24 @@ export const getOverallBucketsSchema = schema.object({ }); export const getCategoriesSchema = schema.object({ - /** Category id */ + /** Category ID */ categoryId: schema.string(), - /** Job id */ + /** Job ID */ jobId: schema.string(), }); +export const getModelSnapshotsSchema = schema.object({ + /** Snapshot ID */ + snapshotId: schema.maybe(schema.string()), + /** Job ID */ + jobId: schema.string(), +}); + +export const updateModelSnapshotSchema = schema.object({ + /** description */ + description: schema.maybe(schema.string()), + /** retain */ + retain: schema.maybe(schema.boolean()), +}); + export const forecastAnomalyDetector = schema.object({ duration: schema.any() }); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index be107db9508fd..36e340adf0b31 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -66,3 +66,20 @@ export const updateGroupsSchema = { ) ), }; + +export const revertModelSnapshotSchema = schema.object({ + jobId: schema.string(), + snapshotId: schema.string(), + replay: schema.boolean(), + end: schema.maybe(schema.number()), + deleteInterveningResults: schema.maybe(schema.boolean()), + calendarEvents: schema.maybe( + schema.arrayOf( + schema.object({ + start: schema.number(), + end: schema.number(), + description: schema.string(), + }) + ) + ), +}); diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx index 072d3c47d3a55..696361393ef82 100644 --- a/x-pack/plugins/observability/public/pages/home/index.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.tsx @@ -10,7 +10,6 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, - EuiHorizontalRule, EuiIcon, EuiImage, EuiSpacer, @@ -21,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { appsSection, tryItOutItemsSection } from './section'; +import { appsSection } from './section'; const Container = styled.div` min-height: calc(100vh - 48px); @@ -158,46 +157,6 @@ export const Home = () => { - - - - {/* Try it out */} - - - - -

- {i18n.translate('xpack.observability.home.tryItOut', { - defaultMessage: 'Try it out', - })} -

-
-
-
-
- - {/* Try it out sections */} - - - {tryItOutItemsSection.map((item) => ( - - } - title={ - -

{item.title}

-
- } - description={item.description} - target={item.target} - href={item.href} - /> -
- ))} -
- -
diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index f8bbfbfa30548..a2b82c31bf2ab 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -60,25 +60,3 @@ export const appsSection: ISection[] = [ }), }, ]; - -export const tryItOutItemsSection: ISection[] = [ - { - id: 'demo', - title: i18n.translate('xpack.observability.section.tryItOut.demo.title', { - defaultMessage: 'Demo Playground', - }), - icon: 'play', - description: '', - href: 'https://demo.elastic.co/', - target: '_blank', - }, - { - id: 'sampleData', - title: i18n.translate('xpack.observability.section.tryItOut.sampleData.title', { - defaultMessage: 'Add sample data', - }), - icon: 'documents', - description: '', - href: '/app/home#/tutorial_directory/sampleData', - }, -]; diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index fcaa295a45ecc..aad3d9b026c6e 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -155,8 +155,6 @@ export class ReportingPublicPlugin implements Plugin { ); } - // FIXME: only perform these actions for authenticated routes - // Depends on https://github.com/elastic/kibana/pull/39477 public start(core: CoreStart) { const { http, notifications } = core; const apiClient = new ReportingAPIClient(http); diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts index e5124c80601d7..579b5196ad4d9 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/decrypt_job_headers.ts @@ -14,14 +14,14 @@ interface HasEncryptedHeaders { // TODO merge functionality with CSV execute job export const decryptJobHeaders = async < JobParamsType, - JobDocPayloadType extends HasEncryptedHeaders + ScheduledTaskParamsType extends HasEncryptedHeaders >({ encryptionKey, job, logger, }: { encryptionKey?: string; - job: JobDocPayloadType; + job: ScheduledTaskParamsType; logger: LevelLogger; }): Promise> => { try { diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts index 5d651ad5f8aea..030ced5dc4b80 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.test.ts @@ -8,8 +8,8 @@ import sinon from 'sinon'; import { ReportingConfig } from '../../../'; import { ReportingCore } from '../../../core'; import { createMockReportingCore } from '../../../test_helpers'; -import { JobDocPayload } from '../../../types'; -import { JobDocPayloadPDF } from '../../printable_pdf/types'; +import { ScheduledTaskParams } from '../../../types'; +import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './index'; let mockConfig: ReportingConfig; @@ -37,7 +37,7 @@ describe('conditions', () => { }; const conditionalHeaders = await getConditionalHeaders({ - job: {} as JobDocPayload, + job: {} as ScheduledTaskParams, filteredHeaders: permittedHeaders, config: mockConfig, }); @@ -64,14 +64,14 @@ test('uses basePath from job when creating saved object service', async () => { baz: 'quix', }; const conditionalHeaders = await getConditionalHeaders({ - job: {} as JobDocPayload, + job: {} as ScheduledTaskParams, filteredHeaders: permittedHeaders, config: mockConfig, }); const jobBasePath = '/sbp/s/marketing'; await getCustomLogo({ reporting: mockReportingPlugin, - job: { basePath: jobBasePath } as JobDocPayloadPDF, + job: { basePath: jobBasePath } as ScheduledTaskParamsPDF, conditionalHeaders, config: mockConfig, }); @@ -94,14 +94,14 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav baz: 'quix', }; const conditionalHeaders = await getConditionalHeaders({ - job: {} as JobDocPayload, + job: {} as ScheduledTaskParams, filteredHeaders: permittedHeaders, config: mockConfig, }); await getCustomLogo({ reporting: mockReportingPlugin, - job: {} as JobDocPayloadPDF, + job: {} as ScheduledTaskParamsPDF, conditionalHeaders, config: mockConfig, }); @@ -139,7 +139,7 @@ describe('config formatting', () => { mockConfig = getMockConfig(mockConfigGet); const conditionalHeaders = await getConditionalHeaders({ - job: {} as JobDocPayload, + job: {} as ScheduledTaskParams, filteredHeaders: {}, config: mockConfig, }); diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts index 6854f678aa975..7a50eaac80d85 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_conditional_headers.ts @@ -7,13 +7,13 @@ import { ReportingConfig } from '../../../'; import { ConditionalHeaders } from '../../../types'; -export const getConditionalHeaders = ({ +export const getConditionalHeaders = ({ config, job, filteredHeaders, }: { config: ReportingConfig; - job: JobDocPayloadType; + job: ScheduledTaskParamsType; filteredHeaders: Record; }) => { const { kbnConfig } = config; diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts index bd6eb4644d87f..c364752c8dd0f 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.test.ts @@ -6,7 +6,7 @@ import { ReportingCore } from '../../../core'; import { createMockReportingCore } from '../../../test_helpers'; -import { JobDocPayloadPDF } from '../../printable_pdf/types'; +import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; import { getConditionalHeaders, getCustomLogo } from './index'; const mockConfigGet = jest.fn().mockImplementation((key: string) => { @@ -37,7 +37,7 @@ test(`gets logo from uiSettings`, async () => { }); const conditionalHeaders = await getConditionalHeaders({ - job: {} as JobDocPayloadPDF, + job: {} as ScheduledTaskParamsPDF, filteredHeaders: permittedHeaders, config: mockConfig, }); @@ -45,7 +45,7 @@ test(`gets logo from uiSettings`, async () => { const { logo } = await getCustomLogo({ reporting: mockReportingPlugin, config: mockConfig, - job: {} as JobDocPayloadPDF, + job: {} as ScheduledTaskParamsPDF, conditionalHeaders, }); diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts index 85d1272fc22ce..36c02eb47565c 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_custom_logo.ts @@ -7,7 +7,7 @@ import { ReportingConfig, ReportingCore } from '../../../'; import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../../common/constants'; import { ConditionalHeaders } from '../../../types'; -import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only +import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; // Logo is PDF only export const getCustomLogo = async ({ reporting, @@ -17,7 +17,7 @@ export const getCustomLogo = async ({ }: { reporting: ReportingCore; config: ReportingConfig; - job: JobDocPayloadPDF; + job: ScheduledTaskParamsPDF; conditionalHeaders: ConditionalHeaders; }) => { const serverBasePath: string = config.kbnConfig.get('server', 'basePath'); diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts index cacea41477ea4..ad952c084d4f3 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.test.ts @@ -5,12 +5,12 @@ */ import { ReportingConfig } from '../../../'; -import { JobDocPayloadPNG } from '../../png/types'; -import { JobDocPayloadPDF } from '../../printable_pdf/types'; +import { ScheduledTaskParamsPNG } from '../../png/types'; +import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; import { getFullUrls } from './get_full_urls'; interface FullUrlsOpts { - job: JobDocPayloadPNG & JobDocPayloadPDF; + job: ScheduledTaskParamsPNG & ScheduledTaskParamsPDF; config: ReportingConfig; } @@ -35,7 +35,7 @@ beforeEach(() => { mockConfig = getMockConfig(mockConfigGet); }); -const getMockJob = (base: object) => base as JobDocPayloadPNG & JobDocPayloadPDF; +const getMockJob = (base: object) => base as ScheduledTaskParamsPNG & ScheduledTaskParamsPDF; test(`fails if no URL is passed`, async () => { const fn = () => getFullUrls({ job: getMockJob({}), config: mockConfig } as FullUrlsOpts); diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts index bcd7f122748cb..67bc8d16fa758 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/get_full_urls.ts @@ -13,22 +13,26 @@ import { import { ReportingConfig } from '../../..'; import { getAbsoluteUrlFactory } from '../../../../common/get_absolute_url'; import { validateUrls } from '../../../../common/validate_urls'; -import { JobDocPayloadPNG } from '../../png/types'; -import { JobDocPayloadPDF } from '../../printable_pdf/types'; +import { ScheduledTaskParamsPNG } from '../../png/types'; +import { ScheduledTaskParamsPDF } from '../../printable_pdf/types'; -function isPngJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPNG { - return (job as JobDocPayloadPNG).relativeUrl !== undefined; +function isPngJob( + job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF +): job is ScheduledTaskParamsPNG { + return (job as ScheduledTaskParamsPNG).relativeUrl !== undefined; } -function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPDF { - return (job as JobDocPayloadPDF).relativeUrls !== undefined; +function isPdfJob( + job: ScheduledTaskParamsPNG | ScheduledTaskParamsPDF +): job is ScheduledTaskParamsPDF { + return (job as ScheduledTaskParamsPDF).relativeUrls !== undefined; } -export function getFullUrls({ +export function getFullUrls({ config, job, }: { config: ReportingConfig; - job: JobDocPayloadPDF | JobDocPayloadPNG; + job: ScheduledTaskParamsPDF | ScheduledTaskParamsPNG; }) { const [basePath, protocol, hostname, port] = [ config.kbnConfig.get('server', 'basePath'), diff --git a/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts index 5147881a980ea..db7137c30513b 100644 --- a/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts +++ b/x-pack/plugins/reporting/server/export_types/common/execute_job/omit_blacklisted_headers.ts @@ -9,11 +9,11 @@ import { KBN_SCREENSHOT_HEADER_BLACKLIST_STARTS_WITH_PATTERN, } from '../../../../common/constants'; -export const omitBlacklistedHeaders = ({ +export const omitBlacklistedHeaders = ({ job, decryptedHeaders, }: { - job: JobDocPayloadType; + job: ScheduledTaskParamsType; decryptedHeaders: Record; }) => { const filteredHeaders: Record = omit( diff --git a/x-pack/plugins/reporting/server/export_types/csv/index.ts b/x-pack/plugins/reporting/server/export_types/csv/index.ts index 8642a6d5758a8..b5eacdfc62c8b 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/index.ts @@ -15,21 +15,21 @@ import { import { CSV_JOB_TYPE as jobType } from '../../../constants'; import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; import { metadata } from './metadata'; -import { createJobFactory } from './server/create_job'; -import { executeJobFactory } from './server/execute_job'; -import { JobDocPayloadDiscoverCsv, JobParamsDiscoverCsv } from './types'; +import { scheduleTaskFnFactory } from './server/create_job'; +import { runTaskFnFactory } from './server/execute_job'; +import { JobParamsDiscoverCsv, ScheduledTaskParamsCSV } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsDiscoverCsv, ESQueueCreateJobFn, - JobDocPayloadDiscoverCsv, - ESQueueWorkerExecuteFn + ScheduledTaskParamsCSV, + ESQueueWorkerExecuteFn > => ({ ...metadata, jobType, jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, + scheduleTaskFnFactory, + runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, LICENSE_TYPE_BASIC, diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts index acf7f0505a735..c4fa1cd8e4fa6 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/create_job.ts @@ -4,24 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { ReportingCore } from '../../../'; import { cryptoFactory } from '../../../lib'; -import { CreateJobFactory, ESQueueCreateJobFn } from '../../../types'; +import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../types'; import { JobParamsDiscoverCsv } from '../types'; -export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore) { +>> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); const setupDeps = reporting.getPluginSetupDeps(); - return async function createJob( - jobParams: JobParamsDiscoverCsv, - context: RequestHandlerContext, - request: KibanaRequest - ) { + return async function scheduleTask(jobParams, context, request) { const serializedEncryptedHeaders = await crypto.encrypt(request.headers); const savedObjectsClient = context.core.savedObjects.client; diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts index 4ce448e953bd1..d1297454971c7 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.test.ts @@ -20,8 +20,8 @@ import { CSV_BOM_CHARS } from '../../../../common/constants'; import { LevelLogger } from '../../../lib'; import { setFieldFormats } from '../../../services'; import { createMockReportingCore } from '../../../test_helpers'; -import { JobDocPayloadDiscoverCsv } from '../types'; -import { executeJobFactory } from './execute_job'; +import { ScheduledTaskParamsCSV } from '../types'; +import { runTaskFnFactory } from './execute_job'; const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve(), ms)); @@ -30,7 +30,7 @@ const getRandomScrollId = () => { return puid.generate(); }; -const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadDiscoverCsv; +const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsCSV; describe('CSV Execute Job', function () { const encryptionKey = 'testEncryptionKey'; @@ -125,10 +125,10 @@ describe('CSV Execute Job', function () { describe('basic Elasticsearch call behavior', function () { it('should decrypt encrypted headers and pass to callAsCurrentUser', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - await executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + await runTask( 'job456', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -145,8 +145,8 @@ describe('CSV Execute Job', function () { testBody: true, }; - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const job = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const job = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { @@ -155,7 +155,7 @@ describe('CSV Execute Job', function () { }, }); - await executeJob('job777', job, cancellationToken); + await runTask('job777', job, cancellationToken); const searchCall = callAsCurrentUserStub.firstCall; expect(searchCall.args[0]).toBe('search'); @@ -172,10 +172,10 @@ describe('CSV Execute Job', function () { _scroll_id: scrollId, }); callAsCurrentUserStub.onSecondCall().resolves(defaultElasticsearchResponse); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - await executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + await runTask( 'job456', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -190,10 +190,10 @@ describe('CSV Execute Job', function () { }); it('should not execute scroll if there are no hits from the search', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - await executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + await runTask( 'job456', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -224,10 +224,10 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - await executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + await runTask( 'job456', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -263,10 +263,10 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - await executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + await runTask( 'job456', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -295,16 +295,16 @@ describe('CSV Execute Job', function () { _scroll_id: lastScrollId, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: undefined, searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot(`[TypeError: Cannot read property 'indexOf' of undefined]`); + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( + `[TypeError: Cannot read property 'indexOf' of undefined]` + ); const lastCall = callAsCurrentUserStub.getCall(callAsCurrentUserStub.callCount - 1); expect(lastCall.args[0]).toBe('clearScroll'); @@ -322,14 +322,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { csv_contains_formulas: csvContainsFormulas } = await executeJob( + const { csv_contains_formulas: csvContainsFormulas } = await runTask( 'job123', jobParams, cancellationToken @@ -347,14 +347,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { csv_contains_formulas: csvContainsFormulas } = await executeJob( + const { csv_contains_formulas: csvContainsFormulas } = await runTask( 'job123', jobParams, cancellationToken @@ -373,14 +373,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { csv_contains_formulas: csvContainsFormulas } = await executeJob( + const { csv_contains_formulas: csvContainsFormulas } = await runTask( 'job123', jobParams, cancellationToken @@ -399,15 +399,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['=SUM(A1:A2)', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { csv_contains_formulas: csvContainsFormulas } = await executeJob( + const { csv_contains_formulas: csvContainsFormulas } = await runTask( 'job123', jobParams, cancellationToken @@ -425,14 +425,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { csv_contains_formulas: csvContainsFormulas } = await executeJob( + const { csv_contains_formulas: csvContainsFormulas } = await runTask( 'job123', jobParams, cancellationToken @@ -452,14 +452,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toEqual(`${CSV_BOM_CHARS}one,two\none,bar\n`); }); @@ -473,14 +473,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toEqual('one,two\none,bar\n'); }); @@ -496,14 +496,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toEqual("one,two\n\"'=cmd|' /C calc'!A0\",bar\n"); }); @@ -517,14 +517,14 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toEqual('one,two\n"=cmd|\' /C calc\'!A0",bar\n'); }); @@ -533,15 +533,15 @@ describe('CSV Execute Job', function () { describe('Elasticsearch call errors', function () { it('should reject Promise if search call errors out', async function () { callAsCurrentUserStub.rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot(`[Error]`); + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( + `[Error]` + ); }); it('should reject Promise if scroll call errors out', async function () { @@ -552,15 +552,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); callAsCurrentUserStub.onSecondCall().rejects(new Error()); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot(`[Error]`); + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( + `[Error]` + ); }); }); @@ -573,15 +573,13 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot( + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( `[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[{}]}}]` ); }); @@ -594,15 +592,13 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot( + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( `[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[]}}]` ); }); @@ -622,15 +618,13 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot( + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( `[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[{}]}}]` ); }); @@ -650,15 +644,13 @@ describe('CSV Execute Job', function () { _scroll_id: undefined, }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, }); - await expect( - executeJob('job123', jobParams, cancellationToken) - ).rejects.toMatchInlineSnapshot( + await expect(runTask('job123', jobParams, cancellationToken)).rejects.toMatchInlineSnapshot( `[Error: Expected _scroll_id in the following Elasticsearch response: {"hits":{"hits":[]}}]` ); }); @@ -686,10 +678,10 @@ describe('CSV Execute Job', function () { }); it('should stop calling Elasticsearch when cancellationToken.cancel is called', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + runTask( 'job345', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -705,10 +697,10 @@ describe('CSV Execute Job', function () { }); it(`shouldn't call clearScroll if it never got a scrollId`, async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + runTask( 'job345', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -723,10 +715,10 @@ describe('CSV Execute Job', function () { }); it('should call clearScroll if it got a scrollId', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - executeJob( + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + runTask( 'job345', - getJobDocPayload({ + getScheduledTaskParams({ headers: encryptedHeaders, fields: [], searchRequest: { index: null, body: null }, @@ -745,54 +737,54 @@ describe('CSV Execute Job', function () { describe('csv content', function () { it('should write column headers to output, even if there are no results', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toBe(`one,two\n`); }); it('should use custom uiSettings csv:separator for header', async function () { mockUiSettingsClient.get.withArgs(CSV_SEPARATOR_SETTING).returns(';'); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toBe(`one;two\n`); }); it('should escape column headers if uiSettings csv:quoteValues is true', async function () { mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(true); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toBe(`"one and a half",two,"three-and-four","five & six"\n`); }); it(`shouldn't escape column headers if uiSettings csv:quoteValues is false`, async function () { mockUiSettingsClient.get.withArgs(CSV_QUOTE_VALUES_SETTING).returns(false); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one and a half', 'two', 'three-and-four', 'five & six'], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); expect(content).toBe(`one and a half,two,three-and-four,five & six\n`); }); it('should write column headers to output, when there are results', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ one: '1', two: '2' }], @@ -800,19 +792,19 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const jobParams = getJobDocPayload({ + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); const lines = content.split('\n'); const headerLine = lines[0]; expect(headerLine).toBe('one,two'); }); it('should use comma separated values of non-nested fields from _source', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -820,20 +812,20 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const jobParams = getJobDocPayload({ + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); const lines = content.split('\n'); const valuesLine = lines[1]; expect(valuesLine).toBe('foo,bar'); }); it('should concatenate the hits from multiple responses', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -847,13 +839,13 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const jobParams = getJobDocPayload({ + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); const lines = content.split('\n'); expect(lines[1]).toBe('foo,bar'); @@ -861,7 +853,7 @@ describe('CSV Execute Job', function () { }); it('should use field formatters to format fields', async function () { - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); callAsCurrentUserStub.onFirstCall().resolves({ hits: { hits: [{ _source: { one: 'foo', two: 'bar' } }], @@ -869,7 +861,7 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const jobParams = getJobDocPayload({ + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], @@ -884,7 +876,7 @@ describe('CSV Execute Job', function () { }, }, }); - const { content } = await executeJob('job123', jobParams, cancellationToken); + const { content } = await runTask('job123', jobParams, cancellationToken); const lines = content.split('\n'); expect(lines[1]).toBe('FOO,bar'); @@ -903,14 +895,14 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(1); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, }); - ({ content, max_size_reached: maxSizeReached } = await executeJob( + ({ content, max_size_reached: maxSizeReached } = await runTask( 'job123', jobParams, cancellationToken @@ -933,14 +925,14 @@ describe('CSV Execute Job', function () { beforeEach(async function () { configGetStub.withArgs('csv', 'maxSizeBytes').returns(9); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], searchRequest: { index: null, body: null }, }); - ({ content, max_size_reached: maxSizeReached } = await executeJob( + ({ content, max_size_reached: maxSizeReached } = await runTask( 'job123', jobParams, cancellationToken @@ -970,15 +962,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - ({ content, max_size_reached: maxSizeReached } = await executeJob( + ({ content, max_size_reached: maxSizeReached } = await runTask( 'job123', jobParams, cancellationToken @@ -1010,15 +1002,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - ({ content, max_size_reached: maxSizeReached } = await executeJob( + ({ content, max_size_reached: maxSizeReached } = await runTask( 'job123', jobParams, cancellationToken @@ -1047,15 +1039,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - await executeJob('job123', jobParams, cancellationToken); + await runTask('job123', jobParams, cancellationToken); const searchCall = callAsCurrentUserStub.firstCall; expect(searchCall.args[0]).toBe('search'); @@ -1073,15 +1065,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - await executeJob('job123', jobParams, cancellationToken); + await runTask('job123', jobParams, cancellationToken); const searchCall = callAsCurrentUserStub.firstCall; expect(searchCall.args[0]).toBe('search'); @@ -1099,15 +1091,15 @@ describe('CSV Execute Job', function () { _scroll_id: 'scrollId', }); - const executeJob = await executeJobFactory(mockReportingCore, mockLogger); - const jobParams = getJobDocPayload({ + const runTask = await runTaskFnFactory(mockReportingCore, mockLogger); + const jobParams = getScheduledTaskParams({ headers: encryptedHeaders, fields: ['one', 'two'], conflictedTypesFields: [], searchRequest: { index: null, body: null }, }); - await executeJob('job123', jobParams, cancellationToken); + await runTask('job123', jobParams, cancellationToken); const scrollCall = callAsCurrentUserStub.secondCall; expect(scrollCall.args[0]).toBe('scroll'); diff --git a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts index 91a4db0469fb5..89fd014502f74 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/server/execute_job.ts @@ -8,31 +8,26 @@ import { i18n } from '@kbn/i18n'; import Hapi from 'hapi'; import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server'; import { - CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING, + CSV_SEPARATOR_SETTING, } from '../../../../../../../src/plugins/share/server'; -import { ReportingCore } from '../../..'; import { CSV_BOM_CHARS, CSV_JOB_TYPE } from '../../../../common/constants'; import { getFieldFormats } from '../../../../server/services'; -import { cryptoFactory, LevelLogger } from '../../../lib'; -import { ESQueueWorkerExecuteFn, ExecuteJobFactory } from '../../../types'; -import { JobDocPayloadDiscoverCsv } from '../types'; +import { cryptoFactory } from '../../../lib'; +import { ESQueueWorkerExecuteFn, RunTaskFnFactory } from '../../../types'; +import { ScheduledTaskParamsCSV } from '../types'; import { fieldFormatMapFactory } from './lib/field_format_map'; import { createGenerateCsv } from './lib/generate_csv'; -export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { +export const runTaskFnFactory: RunTaskFnFactory> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job']); const serverBasePath = config.kbnConfig.get('server', 'basePath'); - return async function executeJob( - jobId: string, - job: JobDocPayloadDiscoverCsv, - cancellationToken: any - ) { + return async function runTask(jobId, job, cancellationToken) { const elasticsearch = reporting.getElasticsearchService(); const jobLogger = logger.clone([jobId]); diff --git a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts index c80cd5fd24fe5..ab3e114c7c995 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/types.d.ts @@ -5,7 +5,7 @@ */ import { CancellationToken } from '../../../common'; -import { JobParamPostPayload, JobDocPayload, ScrollConfig } from '../../types'; +import { JobParamPostPayload, ScheduledTaskParams, ScrollConfig } from '../../types'; export type RawValue = string | object | null | undefined; @@ -32,7 +32,7 @@ export interface JobParamsDiscoverCsv { post?: JobParamPostPayloadDiscoverCsv; } -export interface JobDocPayloadDiscoverCsv extends JobDocPayload { +export interface ScheduledTaskParamsCSV extends ScheduledTaskParams { basePath: string; searchRequest: any; fields: any; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts index 65802ee5bb7fb..961a046c846e4 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/index.ts @@ -15,16 +15,16 @@ import { import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../constants'; import { ExportTypeDefinition } from '../../types'; import { metadata } from './metadata'; -import { createJobFactory, ImmediateCreateJobFn } from './server/create_job'; -import { executeJobFactory, ImmediateExecuteFn } from './server/execute_job'; +import { ImmediateCreateJobFn, scheduleTaskFnFactory } from './server/create_job'; +import { ImmediateExecuteFn, runTaskFnFactory } from './server/execute_job'; import { JobParamsPanelCsv } from './types'; /* * These functions are exported to share with the API route handler that * generates csv from saved object immediately on request. */ -export { createJobFactory } from './server/create_job'; -export { executeJobFactory } from './server/execute_job'; +export { scheduleTaskFnFactory } from './server/create_job'; +export { runTaskFnFactory } from './server/execute_job'; export const getExportType = (): ExportTypeDefinition< JobParamsPanelCsv, @@ -35,8 +35,8 @@ export const getExportType = (): ExportTypeDefinition< ...metadata, jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, + scheduleTaskFnFactory, + runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, LICENSE_TYPE_BASIC, diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts deleted file mode 100644 index c187da5104d3f..0000000000000 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { notFound, notImplemented } from 'boom'; -import { get } from 'lodash'; -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { ReportingCore } from '../../../..'; -import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../../common/constants'; -import { cryptoFactory, LevelLogger } from '../../../../lib'; -import { CreateJobFactory, TimeRangeParams } from '../../../../types'; -import { - JobDocPayloadPanelCsv, - JobParamsPanelCsv, - SavedObject, - SavedObjectServiceError, - SavedSearchObjectAttributesJSON, - SearchPanel, - VisObjectAttributesJSON, -} from '../../types'; -import { createJobSearch } from './create_job_search'; - -export type ImmediateCreateJobFn = ( - jobParams: JobParamsType, - headers: KibanaRequest['headers'], - context: RequestHandlerContext, - req: KibanaRequest -) => Promise<{ - type: string | null; - title: string; - jobParams: JobParamsType; -}>; - -interface VisData { - title: string; - visType: string; - panel: SearchPanel; -} - -export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { - const config = reporting.getConfig(); - const crypto = cryptoFactory(config.get('encryptionKey')); - const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); - - return async function createJob( - jobParams: JobParamsPanelCsv, - headers: KibanaRequest['headers'], - context: RequestHandlerContext, - req: KibanaRequest - ): Promise { - const { savedObjectType, savedObjectId } = jobParams; - const serializedEncryptedHeaders = await crypto.encrypt(headers); - - const { panel, title, visType }: VisData = await Promise.resolve() - .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) - .then(async (savedObject: SavedObject) => { - const { attributes, references } = savedObject; - const { - kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON, - } = attributes as SavedSearchObjectAttributesJSON; - const { timerange } = req.body as { timerange: TimeRangeParams }; - - if (!kibanaSavedObjectMetaJSON) { - throw new Error('Could not parse saved object data!'); - } - - const kibanaSavedObjectMeta = { - ...kibanaSavedObjectMetaJSON, - searchSource: JSON.parse(kibanaSavedObjectMetaJSON.searchSourceJSON), - }; - - const { visState: visStateJSON } = attributes as VisObjectAttributesJSON; - if (visStateJSON) { - throw notImplemented('Visualization types are not yet implemented'); - } - - // saved search type - return await createJobSearch(timerange, attributes, references, kibanaSavedObjectMeta); - }) - .catch((err: Error) => { - const boomErr = (err as unknown) as { isBoom: boolean }; - if (boomErr.isBoom) { - throw err; - } - const errPayload: SavedObjectServiceError = get(err, 'output.payload', { statusCode: 0 }); - if (errPayload.statusCode === 404) { - throw notFound(errPayload.message); - } - if (err.stack) { - logger.error(err.stack); - } - throw new Error(`Unable to create a job from saved object data! Error: ${err}`); - }); - - return { - headers: serializedEncryptedHeaders, - jobParams: { ...jobParams, panel, visType }, - type: null, - title, - }; - }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts index a3674d69ae6a5..dafac04017607 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/create_job/index.ts @@ -4,4 +4,96 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createJobFactory, ImmediateCreateJobFn } from './create_job'; +import { notFound, notImplemented } from 'boom'; +import { get } from 'lodash'; +import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../../common/constants'; +import { cryptoFactory } from '../../../../lib'; +import { ScheduleTaskFnFactory, TimeRangeParams } from '../../../../types'; +import { + JobParamsPanelCsv, + SavedObject, + SavedObjectServiceError, + SavedSearchObjectAttributesJSON, + SearchPanel, + VisObjectAttributesJSON, +} from '../../types'; +import { createJobSearch } from './create_job_search'; + +export type ImmediateCreateJobFn = ( + jobParams: JobParamsType, + headers: KibanaRequest['headers'], + context: RequestHandlerContext, + req: KibanaRequest +) => Promise<{ + type: string | null; + title: string; + jobParams: JobParamsType; +}>; + +interface VisData { + title: string; + visType: string; + panel: SearchPanel; +} + +export const scheduleTaskFnFactory: ScheduleTaskFnFactory> = function createJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); + const crypto = cryptoFactory(config.get('encryptionKey')); + const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'create-job']); + + return async function scheduleTask(jobParams, headers, context, req) { + const { savedObjectType, savedObjectId } = jobParams; + const serializedEncryptedHeaders = await crypto.encrypt(headers); + + const { panel, title, visType }: VisData = await Promise.resolve() + .then(() => context.core.savedObjects.client.get(savedObjectType, savedObjectId)) + .then(async (savedObject: SavedObject) => { + const { attributes, references } = savedObject; + const { + kibanaSavedObjectMeta: kibanaSavedObjectMetaJSON, + } = attributes as SavedSearchObjectAttributesJSON; + const { timerange } = req.body as { timerange: TimeRangeParams }; + + if (!kibanaSavedObjectMetaJSON) { + throw new Error('Could not parse saved object data!'); + } + + const kibanaSavedObjectMeta = { + ...kibanaSavedObjectMetaJSON, + searchSource: JSON.parse(kibanaSavedObjectMetaJSON.searchSourceJSON), + }; + + const { visState: visStateJSON } = attributes as VisObjectAttributesJSON; + if (visStateJSON) { + throw notImplemented('Visualization types are not yet implemented'); + } + + // saved search type + return await createJobSearch(timerange, attributes, references, kibanaSavedObjectMeta); + }) + .catch((err: Error) => { + const boomErr = (err as unknown) as { isBoom: boolean }; + if (boomErr.isBoom) { + throw err; + } + const errPayload: SavedObjectServiceError = get(err, 'output.payload', { statusCode: 0 }); + if (errPayload.statusCode === 404) { + throw notFound(errPayload.message); + } + if (err.stack) { + logger.error(err.stack); + } + throw new Error(`Unable to create a job from saved object data! Error: ${err}`); + }); + + return { + headers: serializedEncryptedHeaders, + jobParams: { ...jobParams, panel, visType }, + type: null, + title, + }; + }; +}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts index d555100b6320d..26b7a24907f40 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/server/execute_job.ts @@ -6,39 +6,33 @@ import { i18n } from '@kbn/i18n'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { ReportingCore } from '../../..'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; -import { cryptoFactory, LevelLogger } from '../../../lib'; -import { ExecuteJobFactory, JobDocOutput, JobDocPayload } from '../../../types'; +import { cryptoFactory } from '../../../lib'; +import { RunTaskFnFactory, ScheduledTaskParams, TaskRunResult } from '../../../types'; import { CsvResultFromSearch } from '../../csv/types'; -import { FakeRequest, JobDocPayloadPanelCsv, JobParamsPanelCsv, SearchPanel } from '../types'; +import { FakeRequest, JobParamsPanelCsv, SearchPanel } from '../types'; import { createGenerateCsv } from './lib'; /* * ImmediateExecuteFn receives the job doc payload because the payload was - * generated in the CreateFn + * generated in the ScheduleFn */ export type ImmediateExecuteFn = ( jobId: null, - job: JobDocPayload, + job: ScheduledTaskParams, context: RequestHandlerContext, req: KibanaRequest -) => Promise; +) => Promise; -export const executeJobFactory: ExecuteJobFactory> = async function executeJobFactoryFn(reporting: ReportingCore, parentLogger: LevelLogger) { +>> = function executeJobFactoryFn(reporting, parentLogger) { const config = reporting.getConfig(); const crypto = cryptoFactory(config.get('encryptionKey')); const logger = parentLogger.clone([CSV_FROM_SAVEDOBJECT_JOB_TYPE, 'execute-job']); const generateCsv = createGenerateCsv(reporting, parentLogger); - return async function executeJob( - jobId: string | null, - job: JobDocPayloadPanelCsv, - context, - req - ): Promise { + return async function runTask(jobId: string | null, job, context, req) { // There will not be a jobID for "immediate" generation. // jobID is only for "queued" jobs // Use the jobID as a logging tag or "immediate" diff --git a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts index 36ae5b1dac05e..835b352953dfe 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_from_savedobject/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobParamPostPayload, JobDocPayload, TimeRangeParams } from '../../types'; +import { JobParamPostPayload, ScheduledTaskParams, TimeRangeParams } from '../../types'; export interface FakeRequest { headers: Record; @@ -23,7 +23,7 @@ export interface JobParamsPanelCsv { visType?: string; } -export interface JobDocPayloadPanelCsv extends JobDocPayload { +export interface ScheduledTaskParamsPanelCsv extends ScheduledTaskParams { jobParams: JobParamsPanelCsv; } diff --git a/x-pack/plugins/reporting/server/export_types/png/index.ts b/x-pack/plugins/reporting/server/export_types/png/index.ts index a3b51e365e772..b708448b0f8b2 100644 --- a/x-pack/plugins/reporting/server/export_types/png/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/index.ts @@ -14,22 +14,22 @@ import { } from '../../../common/constants'; import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../..//types'; import { metadata } from './metadata'; -import { createJobFactory } from './server/create_job'; -import { executeJobFactory } from './server/execute_job'; -import { JobDocPayloadPNG, JobParamsPNG } from './types'; +import { scheduleTaskFnFactory } from './server/create_job'; +import { runTaskFnFactory } from './server/execute_job'; +import { JobParamsPNG, ScheduledTaskParamsPNG } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPNG, ESQueueCreateJobFn, - JobDocPayloadPNG, - ESQueueWorkerExecuteFn + ScheduledTaskParamsPNG, + ESQueueWorkerExecuteFn > => ({ ...metadata, jobType, jobContentEncoding: 'base64', jobContentExtension: 'PNG', - createJobFactory, - executeJobFactory, + scheduleTaskFnFactory, + runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, LICENSE_TYPE_STANDARD, diff --git a/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts index 3f1556fb29782..f459b8f249c70 100644 --- a/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/server/create_job/index.ts @@ -6,17 +6,17 @@ import { validateUrls } from '../../../../../common/validate_urls'; import { cryptoFactory } from '../../../../lib'; -import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types'; +import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../../types'; import { JobParamsPNG } from '../../types'; -export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function createJob( + return async function scheduleTask( { objectType, title, relativeUrl, browserTimezone, layout }, context, req diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts index b92f53ff6563f..3d3f156aeef02 100644 --- a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.test.ts @@ -9,9 +9,9 @@ import { ReportingCore } from '../../../../'; import { CancellationToken } from '../../../../../common'; import { cryptoFactory, LevelLogger } from '../../../../lib'; import { createMockReportingCore } from '../../../../test_helpers'; -import { JobDocPayloadPNG } from '../../types'; +import { ScheduledTaskParamsPNG } from '../../types'; import { generatePngObservableFactory } from '../lib/generate_png'; -import { executeJobFactory } from './'; +import { runTaskFnFactory } from './'; jest.mock('../lib/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); @@ -36,7 +36,7 @@ const encryptHeaders = async (headers: Record) => { return await crypto.encrypt(headers); }; -const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPNG; +const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPNG; beforeEach(async () => { const kbnConfig = { @@ -81,11 +81,11 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; - await executeJob( + await runTask( 'pngJobId', - getJobDocPayload({ + getScheduledTaskParams({ relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders, @@ -125,15 +125,15 @@ test(`passes browserTimezone to generatePng`, async () => { }); test(`returns content_type of application/png`, async () => { - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of('foo')); - const { content_type: contentType } = await executeJob( + const { content_type: contentType } = await runTask( 'pngJobId', - getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), + getScheduledTaskParams({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), cancellationToken ); expect(contentType).toBe('image/png'); @@ -144,11 +144,11 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ base64: testContent })); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const { content } = await executeJob( + const { content } = await runTask( 'pngJobId', - getJobDocPayload({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), + getScheduledTaskParams({ relativeUrl: '/app/kibana#/something', headers: encryptedHeaders }), cancellationToken ); diff --git a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts index ea4c4b1d106ae..c9ab890dc8a50 100644 --- a/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/server/execute_job/index.ts @@ -7,37 +7,35 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; -import { ReportingCore } from '../../../..'; import { PNG_JOB_TYPE } from '../../../../../common/constants'; -import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../..//types'; -import { LevelLogger } from '../../../../lib'; +import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../..//types'; import { decryptJobHeaders, getConditionalHeaders, getFullUrls, omitBlacklistedHeaders, } from '../../../common/execute_job/'; -import { JobDocPayloadPNG } from '../../types'; +import { ScheduledTaskParamsPNG } from '../../types'; import { generatePngObservableFactory } from '../lib/generate_png'; -type QueuedPngExecutorFactory = ExecuteJobFactory>; +type QueuedPngExecutorFactory = RunTaskFnFactory>; -export const executeJobFactory: QueuedPngExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore, - parentLogger: LevelLogger +export const runTaskFnFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( + reporting, + parentLogger ) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PNG_JOB_TYPE, 'execute']); - return async function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { + return async function runTask(jobId, job, cancellationToken) { const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = logger.clone([jobId]); - const process$: Rx.Observable = Rx.of(1).pipe( + const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), diff --git a/x-pack/plugins/reporting/server/export_types/png/types.d.ts b/x-pack/plugins/reporting/server/export_types/png/types.d.ts index 486a8e91a722f..7a25f4ed8fe73 100644 --- a/x-pack/plugins/reporting/server/export_types/png/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/png/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobDocPayload } from '../../../server/types'; +import { ScheduledTaskParams } from '../../../server/types'; import { LayoutInstance, LayoutParams } from '../common/layouts'; // Job params: structure of incoming user request data @@ -17,7 +17,7 @@ export interface JobParamsPNG { } // Job payload: structure of stored job data provided by create_job -export interface JobDocPayloadPNG extends JobDocPayload { +export interface ScheduledTaskParamsPNG extends ScheduledTaskParams { basePath?: string; browserTimezone: string; forceNow?: string; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts index 39a0cbd5270a1..073bd38b538fb 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/index.ts @@ -14,22 +14,22 @@ import { } from '../../../common/constants'; import { ESQueueCreateJobFn, ESQueueWorkerExecuteFn, ExportTypeDefinition } from '../../types'; import { metadata } from './metadata'; -import { createJobFactory } from './server/create_job'; -import { executeJobFactory } from './server/execute_job'; -import { JobDocPayloadPDF, JobParamsPDF } from './types'; +import { scheduleTaskFnFactory } from './server/create_job'; +import { runTaskFnFactory } from './server/execute_job'; +import { JobParamsPDF, ScheduledTaskParamsPDF } from './types'; export const getExportType = (): ExportTypeDefinition< JobParamsPDF, ESQueueCreateJobFn, - JobDocPayloadPDF, - ESQueueWorkerExecuteFn + ScheduledTaskParamsPDF, + ESQueueWorkerExecuteFn > => ({ ...metadata, jobType, jobContentEncoding: 'base64', jobContentExtension: 'pdf', - createJobFactory, - executeJobFactory, + scheduleTaskFnFactory, + runTaskFnFactory, validLicenses: [ LICENSE_TYPE_TRIAL, LICENSE_TYPE_STANDARD, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts index 06a0902a56954..76c5718249720 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/create_job/index.ts @@ -6,18 +6,18 @@ import { validateUrls } from '../../../../../common/validate_urls'; import { cryptoFactory } from '../../../../lib'; -import { CreateJobFactory, ESQueueCreateJobFn } from '../../../../types'; +import { ESQueueCreateJobFn, ScheduleTaskFnFactory } from '../../../../types'; import { JobParamsPDF } from '../../types'; -export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(reporting) { const config = reporting.getConfig(); const setupDeps = reporting.getPluginSetupDeps(); const crypto = cryptoFactory(config.get('encryptionKey')); - return async function createJobFn( - { title, relativeUrls, browserTimezone, layout, objectType }: JobParamsPDF, + return async function scheduleTaskFn( + { title, relativeUrls, browserTimezone, layout, objectType }, context, req ) { diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts index 2f4ca47cf739e..d4df8ef7072d3 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.test.ts @@ -11,9 +11,9 @@ import { ReportingCore } from '../../../../'; import { CancellationToken } from '../../../../../common'; import { cryptoFactory, LevelLogger } from '../../../../lib'; import { createMockReportingCore } from '../../../../test_helpers'; -import { JobDocPayloadPDF } from '../../types'; +import { ScheduledTaskParamsPDF } from '../../types'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; -import { executeJobFactory } from './'; +import { runTaskFnFactory } from './'; let mockReporting: ReportingCore; @@ -36,7 +36,7 @@ const encryptHeaders = async (headers: Record) => { return await crypto.encrypt(headers); }; -const getJobDocPayload = (baseObj: any) => baseObj as JobDocPayloadPDF; +const getScheduledTaskParams = (baseObj: any) => baseObj as ScheduledTaskParamsPDF; beforeEach(async () => { const kbnConfig = { @@ -79,11 +79,11 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; - await executeJob( + await runTask( 'pdfJobId', - getJobDocPayload({ + getScheduledTaskParams({ title: 'PDF Params Timezone Test', relativeUrl: '/app/kibana#/something', browserTimezone, @@ -98,15 +98,15 @@ test(`passes browserTimezone to generatePdf`, async () => { test(`returns content_type of application/pdf`, async () => { const logger = getMockLogger(); - const executeJob = await executeJobFactory(mockReporting, logger); + const runTask = await runTaskFnFactory(mockReporting, logger); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); - const { content_type: contentType } = await executeJob( + const { content_type: contentType } = await runTask( 'pdfJobId', - getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), + getScheduledTaskParams({ relativeUrls: [], headers: encryptedHeaders }), cancellationToken ); expect(contentType).toBe('application/pdf'); @@ -117,11 +117,11 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - const executeJob = await executeJobFactory(mockReporting, getMockLogger()); + const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const { content } = await executeJob( + const { content } = await runTask( 'pdfJobId', - getJobDocPayload({ relativeUrls: [], headers: encryptedHeaders }), + getScheduledTaskParams({ relativeUrls: [], headers: encryptedHeaders }), cancellationToken ); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts index a4d84b2f9f1e0..7f8f2f4f6906a 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/server/execute_job/index.ts @@ -7,10 +7,8 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; -import { ReportingCore } from '../../../..'; import { PDF_JOB_TYPE } from '../../../../../common/constants'; -import { LevelLogger } from '../../../../lib'; -import { ESQueueWorkerExecuteFn, ExecuteJobFactory, JobDocOutput } from '../../../../types'; +import { ESQueueWorkerExecuteFn, RunTaskFnFactory, TaskRunResult } from '../../../../types'; import { decryptJobHeaders, getConditionalHeaders, @@ -18,21 +16,21 @@ import { getFullUrls, omitBlacklistedHeaders, } from '../../../common/execute_job'; -import { JobDocPayloadPDF } from '../../types'; +import { ScheduledTaskParamsPDF } from '../../types'; import { generatePdfObservableFactory } from '../lib/generate_pdf'; -type QueuedPdfExecutorFactory = ExecuteJobFactory>; +type QueuedPdfExecutorFactory = RunTaskFnFactory>; -export const executeJobFactory: QueuedPdfExecutorFactory = async function executeJobFactoryFn( - reporting: ReportingCore, - parentLogger: LevelLogger +export const runTaskFnFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( + reporting, + parentLogger ) { const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); const logger = parentLogger.clone([PDF_JOB_TYPE, 'execute']); - return async function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { + return async function runTask(jobId, job, cancellationToken) { const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; @@ -40,7 +38,7 @@ export const executeJobFactory: QueuedPdfExecutorFactory = async function execut const generatePdfObservable = await generatePdfObservableFactory(reporting); const jobLogger = logger.clone([jobId]); - const process$: Rx.Observable = Rx.of(1).pipe( + const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders({ encryptionKey, job, logger })), map((decryptedHeaders) => omitBlacklistedHeaders({ job, decryptedHeaders })), map((filteredHeaders) => getConditionalHeaders({ config, job, filteredHeaders })), diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts index 087ef5a6ca82c..5399781a77753 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/types.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobDocPayload } from '../../../server/types'; +import { ScheduledTaskParams } from '../../../server/types'; import { LayoutInstance, LayoutParams } from '../common/layouts'; // Job params: structure of incoming user request data, after being parsed from RISON @@ -17,7 +17,7 @@ export interface JobParamsPDF { } // Job payload: structure of stored job data provided by create_job -export interface JobDocPayloadPDF extends JobDocPayload { +export interface ScheduledTaskParamsPDF extends ScheduledTaskParams { basePath?: string; browserTimezone: string; forceNow?: string; diff --git a/x-pack/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts index d993a17c0b314..5d09af312a41b 100644 --- a/x-pack/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/plugins/reporting/server/lib/create_queue.ts @@ -5,7 +5,7 @@ */ import { ReportingCore } from '../core'; -import { JobDocOutput, JobSource } from '../types'; +import { JobSource, TaskRunResult } from '../types'; import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed import { createWorkerFactory } from './create_worker'; import { Job } from './enqueue_job'; @@ -31,11 +31,11 @@ export interface ESQueueInstance { ) => ESQueueWorker; } -// GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn, +// GenericWorkerFn is a generic for ImmediateExecuteFn | ESQueueWorkerExecuteFn, type GenericWorkerFn = ( jobSource: JobSource, ...workerRestArgs: any[] -) => void | Promise; +) => void | Promise; export async function createQueueFactory( reporting: ReportingCore, diff --git a/x-pack/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/plugins/reporting/server/lib/create_worker.test.ts index 8e1174e01aa7f..85188c07eeb20 100644 --- a/x-pack/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.test.ts @@ -26,7 +26,7 @@ const executeJobFactoryStub = sinon.stub(); const getMockLogger = sinon.stub(); const getMockExportTypesRegistry = ( - exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] + exportTypes: any[] = [{ runTaskFnFactory: executeJobFactoryStub }] ) => ({ getAll: () => exportTypes, @@ -75,11 +75,11 @@ Object { test('Creates a single Esqueue worker for Reporting, even if there are multiple export types', async () => { const exportTypesRegistry = getMockExportTypesRegistry([ - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, + { runTaskFnFactory: executeJobFactoryStub }, + { runTaskFnFactory: executeJobFactoryStub }, + { runTaskFnFactory: executeJobFactoryStub }, + { runTaskFnFactory: executeJobFactoryStub }, + { runTaskFnFactory: executeJobFactoryStub }, ]); mockReporting.getExportTypesRegistry = () => exportTypesRegistry; const createWorker = createWorkerFactory(mockReporting, getMockLogger()); diff --git a/x-pack/plugins/reporting/server/lib/create_worker.ts b/x-pack/plugins/reporting/server/lib/create_worker.ts index c9e865668bb30..837be1f44a093 100644 --- a/x-pack/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/plugins/reporting/server/lib/create_worker.ts @@ -27,7 +27,7 @@ export function createWorkerFactory(reporting: ReportingCore, log for (const exportType of reporting.getExportTypesRegistry().getAll() as Array< ExportTypeDefinition> >) { - const jobExecutor = await exportType.executeJobFactory(reporting, logger); // FIXME: does not "need" to be async + const jobExecutor = exportType.runTaskFnFactory(reporting, logger); jobExecutors.set(exportType.jobType, jobExecutor); } diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts index 3837f593df5b2..625da90f3b4f2 100644 --- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts @@ -52,7 +52,7 @@ export function enqueueJobFactory( context: RequestHandlerContext, request: KibanaRequest ): Promise { - type CreateJobFn = ESQueueCreateJobFn; + type ScheduleTaskFnType = ESQueueCreateJobFn; const username = user ? user.username : false; const esqueue = await reporting.getEsqueue(); const exportType = reporting.getExportTypesRegistry().getById(exportTypeId); @@ -61,8 +61,8 @@ export function enqueueJobFactory( throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); } - const createJob = exportType.createJobFactory(reporting, logger) as CreateJobFn; - const payload = await createJob(jobParams, context, request); + const scheduleTask = exportType.scheduleTaskFnFactory(reporting, logger) as ScheduleTaskFnType; + const payload = await scheduleTask(jobParams, context, request); const options = { timeout: queueTimeout, diff --git a/x-pack/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/plugins/reporting/server/lib/export_types_registry.ts index 893a2635561ff..501989f21103e 100644 --- a/x-pack/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/plugins/reporting/server/lib/export_types_registry.ts @@ -5,15 +5,14 @@ */ import { isString } from 'lodash'; -import memoizeOne from 'memoize-one'; import { getExportType as getTypeCsv } from '../export_types/csv'; import { getExportType as getTypeCsvFromSavedObject } from '../export_types/csv_from_savedobject'; import { getExportType as getTypePng } from '../export_types/png'; import { getExportType as getTypePrintablePdf } from '../export_types/printable_pdf'; import { ExportTypeDefinition } from '../types'; -type GetCallbackFn = ( - item: ExportTypeDefinition +type GetCallbackFn = ( + item: ExportTypeDefinition ) => boolean; // => ExportTypeDefinition @@ -22,8 +21,8 @@ export class ExportTypesRegistry { constructor() {} - register( - item: ExportTypeDefinition + register( + item: ExportTypeDefinition ): void { if (!isString(item.id)) { throw new Error(`'item' must have a String 'id' property `); @@ -33,8 +32,6 @@ export class ExportTypesRegistry { throw new Error(`'item' with id ${item.id} has already been registered`); } - // TODO: Unwrap the execute function from the item's executeJobFactory - // Move that work out of server/lib/create_worker to reduce dependence on ESQueue this._map.set(item.id, item); } @@ -46,24 +43,24 @@ export class ExportTypesRegistry { return this._map.size; } - getById( + getById( id: string - ): ExportTypeDefinition { + ): ExportTypeDefinition { if (!this._map.has(id)) { throw new Error(`Unknown id ${id}`); } return this._map.get(id) as ExportTypeDefinition< JobParamsType, - CreateJobFnType, + ScheduleTaskFnType, JobPayloadType, - ExecuteJobFnType + RunTaskFnType >; } - get( - findType: GetCallbackFn - ): ExportTypeDefinition { + get( + findType: GetCallbackFn + ): ExportTypeDefinition { let result; for (const value of this._map.values()) { if (!findType(value)) { @@ -71,9 +68,9 @@ export class ExportTypesRegistry { } const foundResult: ExportTypeDefinition< JobParamsType, - CreateJobFnType, + ScheduleTaskFnType, JobPayloadType, - ExecuteJobFnType + RunTaskFnType > = value; if (result) { @@ -91,7 +88,7 @@ export class ExportTypesRegistry { } } -function getExportTypesRegistryFn(): ExportTypesRegistry { +export function getExportTypesRegistry(): ExportTypesRegistry { const registry = new ExportTypesRegistry(); /* this replaces the previously async method of registering export types, @@ -108,6 +105,3 @@ function getExportTypesRegistryFn(): ExportTypesRegistry { }); return registry; } - -// FIXME: is this the best way to return a singleton? -export const getExportTypesRegistry = memoizeOne(getExportTypesRegistryFn); diff --git a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 1221f67855410..7d93a36c85bc8 100644 --- a/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -7,12 +7,12 @@ import { schema } from '@kbn/config-schema'; import { ReportingCore } from '../'; import { API_BASE_GENERATE_V1 } from '../../common/constants'; -import { createJobFactory } from '../export_types/csv_from_savedobject/server/create_job'; -import { executeJobFactory } from '../export_types/csv_from_savedobject/server/execute_job'; +import { scheduleTaskFnFactory } from '../export_types/csv_from_savedobject/server/create_job'; +import { runTaskFnFactory } from '../export_types/csv_from_savedobject/server/execute_job'; import { getJobParamsFromRequest } from '../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; -import { JobDocPayloadPanelCsv } from '../export_types/csv_from_savedobject/types'; +import { ScheduledTaskParamsPanelCsv } from '../export_types/csv_from_savedobject/types'; import { LevelLogger as Logger } from '../lib'; -import { JobDocOutput } from '../types'; +import { TaskRunResult } from '../types'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; import { HandlerErrorFunction } from './types'; @@ -36,8 +36,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: - * - re-use the createJob function to build up es query config - * - re-use the executeJob function to run the scan and scroll queries and capture the entire CSV in a result object. + * - re-use the scheduleTask function to build up es query config + * - re-use the runTask function to run the scan and scroll queries and capture the entire CSV in a result object. */ router.post( { @@ -60,11 +60,11 @@ export function registerGenerateCsvFromSavedObjectImmediate( userHandler(async (user, context, req, res) => { const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(req, { isImmediate: true }); - const createJobFn = createJobFactory(reporting, logger); - const executeJobFn = await executeJobFactory(reporting, logger); // FIXME: does not "need" to be async + const scheduleTaskFn = scheduleTaskFnFactory(reporting, logger); + const runTaskFn = runTaskFnFactory(reporting, logger); try { - const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( + const jobDocPayload: ScheduledTaskParamsPanelCsv = await scheduleTaskFn( jobParams, req.headers, context, @@ -74,13 +74,13 @@ export function registerGenerateCsvFromSavedObjectImmediate( content_type: jobOutputContentType, content: jobOutputContent, size: jobOutputSize, - }: JobDocOutput = await executeJobFn(null, jobDocPayload, context, req); + }: TaskRunResult = await runTaskFn(null, jobDocPayload, context, req); logger.info(`Job output size: ${jobOutputSize} bytes`); /* * ESQueue worker function defaults `content` to null, even if the - * executeJob returned undefined. + * runTask returned undefined. * * This converts null to undefined so the value can be sent to h.response() */ diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index e16f5278c8cc7..93f79bfd892b9 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -10,7 +10,7 @@ import * as _ from 'lodash'; import { CSV_JOB_TYPE } from '../../../common/constants'; import { statuses } from '../../lib/esqueue/constants/statuses'; import { ExportTypesRegistry } from '../../lib/export_types_registry'; -import { ExportTypeDefinition, JobDocOutput, JobSource } from '../../types'; +import { ExportTypeDefinition, JobSource, TaskRunResult } from '../../types'; type ExportTypeType = ExportTypeDefinition; @@ -18,7 +18,7 @@ interface ErrorFromPayload { message: string; } -// A camelCase version of JobDocOutput +// A camelCase version of TaskRunResult interface Payload { statusCode: number; content: string | Buffer | ErrorFromPayload; @@ -31,7 +31,7 @@ const DEFAULT_TITLE = 'report'; const getTitle = (exportType: ExportTypeType, title?: string): string => `${title || DEFAULT_TITLE}.${exportType.jobContentExtension}`; -const getReportingHeaders = (output: JobDocOutput, exportType: ExportTypeType) => { +const getReportingHeaders = (output: TaskRunResult, exportType: ExportTypeType) => { const metaDataHeaders: Record = {}; if (exportType.jobType === CSV_JOB_TYPE) { @@ -55,7 +55,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist } } - function getCompleted(output: JobDocOutput, jobType: string, title: string): Payload { + function getCompleted(output: TaskRunResult, jobType: string, title: string): Payload { const exportType = exportTypesRegistry.get((item: ExportTypeType) => item.jobType === jobType); const filename = getTitle(exportType, title); const headers = getReportingHeaders(output, exportType); @@ -73,7 +73,7 @@ export function getDocumentPayloadFactory(exportTypesRegistry: ExportTypesRegist // @TODO: These should be semantic HTTP codes as 500/503's indicate // error then these are really operating properly. - function getFailure(output: JobDocOutput): Payload { + function getFailure(output: TaskRunResult): Payload { return { statusCode: 500, content: { diff --git a/x-pack/plugins/reporting/server/routes/types.d.ts b/x-pack/plugins/reporting/server/routes/types.d.ts index 5eceed0a7f2ab..607ce34ab9465 100644 --- a/x-pack/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/plugins/reporting/server/routes/types.d.ts @@ -6,7 +6,7 @@ import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; import { AuthenticatedUser } from '../../../security/server'; -import { JobDocPayload } from '../types'; +import { ScheduledTaskParams } from '../types'; export type HandlerFunction = ( user: AuthenticatedUser | null, @@ -23,7 +23,7 @@ export interface QueuedJobPayload { error?: boolean; source: { job: { - payload: JobDocPayload; + payload: ScheduledTaskParams; }; }; } diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index 409a89899bee0..96eef81672610 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -58,7 +58,7 @@ export interface JobParamPostPayload { timerange: TimeRangeParams; } -export interface JobDocPayload { +export interface ScheduledTaskParams { headers?: string; // serialized encrypted headers jobParams: JobParamsType; title: string; @@ -70,13 +70,13 @@ export interface JobSource { _index: string; _source: { jobtype: string; - output: JobDocOutput; - payload: JobDocPayload; + output: TaskRunResult; + payload: ScheduledTaskParams; status: JobStatus; }; } -export interface JobDocOutput { +export interface TaskRunResult { content_type: string; content: string | null; size: number; @@ -173,43 +173,43 @@ export type ReportingSetup = object; * Internal Types */ +export type CaptureConfig = ReportingConfigType['capture']; +export type ScrollConfig = ReportingConfigType['csv']['scroll']; + export type ESQueueCreateJobFn = ( jobParams: JobParamsType, context: RequestHandlerContext, request: KibanaRequest ) => Promise; -export type ESQueueWorkerExecuteFn = ( +export type ESQueueWorkerExecuteFn = ( jobId: string, - job: JobDocPayloadType, - cancellationToken?: CancellationToken + job: ScheduledTaskParamsType, + cancellationToken: CancellationToken ) => Promise; -export type CaptureConfig = ReportingConfigType['capture']; -export type ScrollConfig = ReportingConfigType['csv']['scroll']; - -export type CreateJobFactory = ( +export type ScheduleTaskFnFactory = ( reporting: ReportingCore, logger: LevelLogger -) => CreateJobFnType; +) => ScheduleTaskFnType; -export type ExecuteJobFactory = ( +export type RunTaskFnFactory = ( reporting: ReportingCore, logger: LevelLogger -) => Promise; // FIXME: does not "need" to be async +) => RunTaskFnType; export interface ExportTypeDefinition< JobParamsType, - CreateJobFnType, + ScheduleTaskFnType, JobPayloadType, - ExecuteJobFnType + RunTaskFnType > { id: string; name: string; jobType: string; jobContentEncoding?: string; jobContentExtension: string; - createJobFactory: CreateJobFactory; - executeJobFactory: ExecuteJobFactory; + scheduleTaskFnFactory: ScheduleTaskFnFactory; + runTaskFnFactory: RunTaskFnFactory; validLicenses: string[]; } diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 93883eeff5271..0c7bcdefd360d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -235,6 +235,7 @@ export const pageOrUndefined = t.union([page, t.undefined]); export type PageOrUndefined = t.TypeOf; export const signal_ids = t.array(t.string); +export type SignalIds = t.TypeOf; // TODO: Can this be more strict or is this is the set of all Elastic Queries? export const signal_status_query = t.object; diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index e67eb3182ffa9..4f255bb6d6834 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -197,6 +197,32 @@ export interface SavedTimeline extends runtimeTypes.TypeOf {} +/* + * Timeline IDs + */ + +export enum TimelineId { + hostsPageEvents = 'hosts-page-events', + hostsPageExternalAlerts = 'hosts-page-external-alerts', + alertsRulesDetailsPage = 'alerts-rules-details-page', + alertsPage = 'alerts-page', + networkPageExternalAlerts = 'network-page-external-alerts', + active = 'timeline-1', + test = 'test', // Reserved for testing purposes +} + +export const TimelineIdLiteralRt = runtimeTypes.union([ + runtimeTypes.literal(TimelineId.hostsPageEvents), + runtimeTypes.literal(TimelineId.hostsPageExternalAlerts), + runtimeTypes.literal(TimelineId.alertsRulesDetailsPage), + runtimeTypes.literal(TimelineId.alertsPage), + runtimeTypes.literal(TimelineId.networkPageExternalAlerts), + runtimeTypes.literal(TimelineId.active), + runtimeTypes.literal(TimelineId.test), +]); + +export type TimelineIdLiteral = runtimeTypes.TypeOf; + /** * Timeline Saved object type with metadata */ diff --git a/x-pack/plugins/security_solution/common/validate.test.ts b/x-pack/plugins/security_solution/common/validate.test.ts new file mode 100644 index 0000000000000..032f6d9590168 --- /dev/null +++ b/x-pack/plugins/security_solution/common/validate.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +import { validate } from './validate'; + +describe('validate', () => { + test('it should do a validation correctly', () => { + const schema = t.exact(t.type({ a: t.number })); + const payload = { a: 1 }; + const [validated, errors] = validate(payload, schema); + + expect(validated).toEqual(payload); + expect(errors).toEqual(null); + }); + + test('it should do an in-validation correctly', () => { + const schema = t.exact(t.type({ a: t.number })); + const payload = { a: 'some other value' }; + const [validated, errors] = validate(payload, schema); + + expect(validated).toEqual(null); + expect(errors).toEqual('Invalid value "some other value" supplied to "a"'); + }); +}); diff --git a/x-pack/plugins/security_solution/common/validate.ts b/x-pack/plugins/security_solution/common/validate.ts new file mode 100644 index 0000000000000..db9e286e2ebc2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/validate.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 { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as t from 'io-ts'; +import { exactCheck } from './exact_check'; +import { formatErrors } from './format_errors'; + +export const validate = ( + obj: object, + schema: T +): [t.TypeOf | null, string | null] => { + const decoded = schema.decode(obj); + const checked = exactCheck(obj, decoded); + const left = (errors: t.Errors): [T | null, string | null] => [ + null, + formatErrors(errors).join(','), + ]; + const right = (output: T): [T | null, string | null] => [output, null]; + return pipe(checked, fold(left, right)); +}; diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts index 82395de91abfa..b2d35f3f0c336 100644 --- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts @@ -29,12 +29,12 @@ import { dragAndDropColumn, openEventsViewerFieldsBrowser, opensInspectQueryModal, - resetFields, waitsForEventsToBeLoaded, } from '../tasks/hosts/events'; import { clearSearchBar, kqlSearch } from '../tasks/security_header'; import { HOSTS_PAGE } from '../urls/navigation'; +import { resetFields } from '../tasks/timeline'; const defaultHeadersInDefaultEcsCategory = [ { id: '@timestamp' }, diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.ts new file mode 100644 index 0000000000000..a4352f58e6fc7 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_local_storage.spec.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 { reload } from '../tasks/common'; +import { loginAndWaitForPage } from '../tasks/login'; +import { HOSTS_PAGE } from '../urls/navigation'; +import { openEvents } from '../tasks/hosts/main'; +import { DRAGGABLE_HEADER } from '../screens/timeline'; +import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events'; +import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events'; +import { removeColumn, resetFields } from '../tasks/timeline'; + +describe('persistent timeline', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + openEvents(); + waitsForEventsToBeLoaded(); + }); + + afterEach(() => { + openEventsViewerFieldsBrowser(); + resetFields(); + }); + + it('persist the deletion of a column', () => { + cy.get(DRAGGABLE_HEADER).then((header) => { + const currentNumberOfTimelineColumns = header.length; + const expectedNumberOfTimelineColumns = currentNumberOfTimelineColumns - 1; + + cy.wrap(header).eq(TABLE_COLUMN_EVENTS_MESSAGE).invoke('text').should('equal', 'message'); + removeColumn(TABLE_COLUMN_EVENTS_MESSAGE); + + cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns); + + reload(waitsForEventsToBeLoaded); + + cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns); + cy.get(DRAGGABLE_HEADER).each(($el) => { + expect($el.text()).not.equal('message'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts index ed46a90c872c8..a946fefe273e1 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts @@ -36,7 +36,4 @@ export const LOCAL_EVENTS_COUNT = export const LOAD_MORE = '[data-test-subj="events-viewer-panel"] [data-test-subj="TimelineMoreButton"'; -export const RESET_FIELDS = - '[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]'; - export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/hosts/external_events.ts b/x-pack/plugins/security_solution/cypress/screens/hosts/external_events.ts new file mode 100644 index 0000000000000..982e1ea378e70 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/external_events.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const TABLE_COLUMN_EVENTS_MESSAGE = 1; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index bb232b752994a..c673cf34b6dae 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -21,6 +21,11 @@ export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; +export const REMOVE_COLUMN = '[data-test-subj="remove-column"]'; + +export const RESET_FIELDS = + '[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]'; + export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; @@ -30,6 +35,8 @@ export const TIMELINE = (id: string) => { return `[data-test-subj="title-${id}"]`; }; +export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]'; + export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; export const TIMELINE_DATA_PROVIDERS_EMPTY = diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index b0c64214459f0..a385ad78f63b7 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -48,3 +48,9 @@ export const drop = (dropTarget: JQuery) => { .trigger('mouseup', { force: true }) .wait(1000); }; + +export const reload = (afterReload: () => void) => { + cy.reload(); + cy.contains('a', 'Security'); + afterReload(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts index a593650989259..57c819d967883 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -13,7 +13,6 @@ import { HOST_GEO_COUNTRY_NAME_CHECKBOX, INSPECT_QUERY, LOAD_MORE, - RESET_FIELDS, SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; import { DRAGGABLE_HEADER } from '../../screens/timeline'; @@ -53,10 +52,6 @@ export const opensInspectQueryModal = () => { .click({ force: true }); }; -export const resetFields = () => { - cy.get(RESET_FIELDS).click({ force: true }); -}; - export const waitsForEventsToBeLoaded = () => { cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 38da611428b2e..9e17433090c2b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -21,6 +21,8 @@ import { TIMELINE_TITLE, TIMESTAMP_TOGGLE_FIELD, TOGGLE_TIMELINE_EXPAND_EVENT, + REMOVE_COLUMN, + RESET_FIELDS, } from '../screens/timeline'; import { drag, drop } from '../tasks/common'; @@ -101,3 +103,12 @@ export const dragAndDropIdToggleFieldToTimeline = () => { drop(headersDropArea) ); }; + +export const removeColumn = (column: number) => { + cy.get(REMOVE_COLUMN).first().should('exist'); + cy.get(REMOVE_COLUMN).eq(column).click({ force: true }); +}; + +export const resetFields = () => { + cy.get(RESET_FIELDS).click({ force: true }); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx index 51fdd828bcddb..f843bf6881846 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx @@ -7,12 +7,14 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsTableComponent } from './index'; describe('AlertsTableComponent', () => { it('renders correctly', () => { const wrapper = shallow( = ({ + timelineId, canUserCRUD, clearEventsDeleted, clearEventsLoading, @@ -140,18 +141,16 @@ export const AlertsTableComponent: React.FC = ({ const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { - setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading }); + setEventsLoading!({ id: timelineId, eventIds, isLoading }); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [setEventsLoading, ALERTS_TABLE_TIMELINE_ID] + [setEventsLoading, timelineId] ); const setEventsDeletedCallback = useCallback( ({ eventIds, isDeleted }: SetEventsDeletedProps) => { - setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted }); + setEventsDeleted!({ id: timelineId, eventIds, isDeleted }); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [setEventsDeleted, ALERTS_TABLE_TIMELINE_ID] + [setEventsDeleted, timelineId] ); const onAlertStatusUpdateSuccess = useCallback( @@ -202,20 +201,20 @@ export const AlertsTableComponent: React.FC = ({ // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( (newFilterGroup: Status) => { - clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID }); - clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID }); - clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearEventsLoading!({ id: timelineId }); + clearEventsDeleted!({ id: timelineId }); + clearSelected!({ id: timelineId }); setFilterGroup(newFilterGroup); }, - [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup, timelineId] ); // Callback for clearing entire selection from utility bar const clearSelectionCallback = useCallback(() => { - clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID }); + clearSelected!({ id: timelineId }); setSelectAll(false); setShowClearSelectionAction(false); - }, [clearSelected, setSelectAll, setShowClearSelectionAction]); + }, [clearSelected, setSelectAll, setShowClearSelectionAction, timelineId]); // Callback for selecting all events on all pages from utility bar // Dispatches to stateful_body's selectAll via TimelineTypeContext props @@ -327,7 +326,7 @@ export const AlertsTableComponent: React.FC = ({ useEffect(() => { initializeTimeline({ - id: ALERTS_TABLE_TIMELINE_ID, + id: timelineId, documentType: i18n.ALERTS_DOCUMENT_TYPE, footerText: i18n.TOTAL_COUNT_OF_ALERTS, loadingText: i18n.LOADING_ALERTS, @@ -338,7 +337,7 @@ export const AlertsTableComponent: React.FC = ({ }, []); useEffect(() => { setTimelineRowActions({ - id: ALERTS_TABLE_TIMELINE_ID, + id: timelineId, queryFields: requiredFieldsForActions, timelineRowActions: additionalActions, }); @@ -365,7 +364,7 @@ export const AlertsTableComponent: React.FC = ({ defaultModel={alertsDefaultModel} end={to} headerFilterGroup={headerFilterGroup} - id={ALERTS_TABLE_TIMELINE_ID} + id={timelineId} start={from} utilityBar={utilityBarCallback} /> @@ -375,9 +374,9 @@ export const AlertsTableComponent: React.FC = ({ const makeMapStateToProps = () => { const getTimeline = timelineSelectors.getTimelineByIdSelector(); const getGlobalInputs = inputsSelectors.globalSelector(); - const mapStateToProps = (state: State) => { - const timeline: TimelineModel = - getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults; + const mapStateToProps = (state: State, ownProps: OwnProps) => { + const { timelineId } = ownProps; + const timeline: TimelineModel = getTimeline(state, timelineId) ?? timelineDefaults; const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline; const globalInputs: inputsModel.InputsRange = getGlobalInputs(state); diff --git a/x-pack/plugins/security_solution/public/alerts/index.ts b/x-pack/plugins/security_solution/public/alerts/index.ts index c1501419a1cf6..1409ad4f54696 100644 --- a/x-pack/plugins/security_solution/public/alerts/index.ts +++ b/x-pack/plugins/security_solution/public/alerts/index.ts @@ -4,15 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; +import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline'; import { getAlertsRoutes } from './routes'; import { SecuritySubPlugin } from '../app/types'; +const ALERTS_TIMELINE_IDS: TimelineIdLiteral[] = [ + TimelineId.alertsRulesDetailsPage, + TimelineId.alertsPage, +]; + export class Alerts { public setup() {} - public start(): SecuritySubPlugin { + public start(storage: Storage): SecuritySubPlugin { return { routes: getAlertsRoutes(), + storageTimelines: { + timelineById: getTimelinesInStorageByIds(storage, ALERTS_TIMELINE_IDS), + }, }; } } diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx index e3eb4666522ad..9e1c9db168bda 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/detection_engine.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { StickyContainer } from 'react-sticky'; import { connect, ConnectedProps } from 'react-redux'; +import { TimelineId } from '../../../../common/types/timeline'; import { GlobalTime } from '../../../common/containers/global_time'; import { indicesExistOrDataTemporarilyUnavailable, @@ -138,6 +139,7 @@ export const DetectionEnginePageComponent: React.FC = ({ /> = ({ {ruleId != null && ( = ({ subPlugins, ...libs }) => { +const StartAppComponent: FC = ({ subPlugins, storage, ...libs }) => { const { routes: subPluginRoutes, store: subPluginsStore } = subPlugins; const { i18n } = useKibana().services; const history = createHashHistory(); @@ -84,6 +85,7 @@ const StartAppComponent: FC = ({ subPlugins, ...libs }) => { createInitialState(subPluginsStore.initialState), subPluginsStore.reducer, libs$.pipe(pluck('apolloClient')), + storage, subPluginsStore.middlewares ); @@ -118,16 +120,17 @@ interface SiemAppComponentProps { subPlugins: SecuritySubPlugins; } -const SiemAppComponent: React.FC = ({ services, subPlugins }) => ( - - - -); +const SiemAppComponent: React.FC = ({ services, subPlugins }) => { + return ( + + + + ); +}; export const SiemApp = memo(SiemAppComponent); diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 4b00dee3b7510..7a905b35710c9 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -18,6 +18,7 @@ import { NavTab } from '../common/components/navigation/types'; import { State, SubPluginsInitReducer } from '../common/store'; import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; +import { TimelineState } from '../timelines/store/timeline/types'; export enum SiemPageName { overview = 'overview', @@ -48,6 +49,7 @@ export interface SecuritySubPluginStore export interface SecuritySubPlugin { routes: React.ReactElement[]; + storageTimelines?: Pick; } type SecuritySubPluginKeyStore = diff --git a/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx index 18c0032f58c3c..c7015ed81701e 100644 --- a/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/add_filter_to_global_search_bar/index.test.tsx @@ -12,6 +12,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../mock'; import { createStore, State } from '../../store'; import { AddFilterToGlobalSearchBar } from '.'; @@ -33,10 +34,11 @@ jest.mock('../../lib/kibana', () => ({ describe('AddFilterToGlobalSearchBar Component', () => { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); mockAddFilters.mockClear(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx index bb6ba01821835..251e0278b11ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useMemo } from 'react'; import { Filter } from '../../../../../../../src/plugins/data/public'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../events_viewer'; import { alertsDefaultModel } from './default_headers'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; @@ -17,7 +18,6 @@ export interface OwnProps { start: number; } -const ALERTS_TABLE_ID = 'alerts-table'; const defaultAlertsFilters: Filter[] = [ { meta: { @@ -52,18 +52,24 @@ const defaultAlertsFilters: Filter[] = [ ]; interface Props { + timelineId: TimelineIdLiteral; endDate: number; startDate: number; pageFilters?: Filter[]; } -const AlertsTableComponent: React.FC = ({ endDate, startDate, pageFilters = [] }) => { +const AlertsTableComponent: React.FC = ({ + timelineId, + endDate, + startDate, + pageFilters = [], +}) => { const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); const { initializeTimeline } = useManageTimeline(); useEffect(() => { initializeTimeline({ - id: ALERTS_TABLE_ID, + id: timelineId, documentType: i18n.ALERTS_DOCUMENT_TYPE, footerText: i18n.TOTAL_COUNT_OF_ALERTS, title: i18n.ALERTS_TABLE_TITLE, @@ -76,7 +82,7 @@ const AlertsTableComponent: React.FC = ({ endDate, startDate, pageFilters pageFilters={alertsFilter} defaultModel={alertsDefaultModel} end={endDate} - id={ALERTS_TABLE_ID} + id={timelineId} start={startDate} /> ); diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx index 8a62a05ecc2da..a31cb4f2a8bfd 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useCallback, useMemo } from 'react'; import numeral from '@elastic/numeral'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; -import { AlertsComponentsQueryProps } from './types'; +import { AlertsComponentsProps } from './types'; import { AlertsTable } from './alerts_table'; import * as i18n from './translations'; import { useUiSetting$ } from '../../lib/kibana'; @@ -17,6 +17,7 @@ import { MatrixHisrogramConfigs } from '../matrix_histogram/types'; const ID = 'alertsOverTimeQuery'; export const AlertsView = ({ + timelineId, deleteQuery, endDate, filterQuery, @@ -24,7 +25,7 @@ export const AlertsView = ({ setQuery, startDate, type, -}: AlertsComponentsQueryProps) => { +}: AlertsComponentsProps) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const getSubtitle = useCallback( (totalCount: number) => @@ -61,7 +62,12 @@ export const AlertsView = ({ type={type} {...alertsHistogramConfigs} /> - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts index 2bc33aaf1bae7..78a6332c90fbc 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts @@ -5,16 +5,19 @@ */ import { Filter } from '../../../../../../../src/plugins/data/public'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types'; import { NetworkComponentQueryProps } from '../../../network/pages/navigation/types'; import { MatrixHistogramOption } from '../matrix_histogram/types'; type CommonQueryProps = HostsComponentsQueryProps | NetworkComponentQueryProps; -export interface AlertsComponentsQueryProps + +export interface AlertsComponentsProps extends Pick< CommonQueryProps, 'deleteQuery' | 'endDate' | 'filterQuery' | 'skip' | 'setQuery' | 'startDate' | 'type' > { + timelineId: TimelineIdLiteral; pageFilters: Filter[]; stackByOptions?: MatrixHistogramOption[]; defaultFilters?: Filter[]; diff --git a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx index 39b17f7008e64..4bc77555f09bd 100644 --- a/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/error_toast_dispatcher/index.test.tsx @@ -8,7 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Provider } from 'react-redux'; -import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../mock'; +import { + apolloClientObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, +} from '../../mock'; import { createStore } from '../../store/store'; import { ErrorToastDispatcher } from '.'; @@ -16,10 +21,11 @@ import { State } from '../../store/types'; describe('Error Toast Dispatcher', () => { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx index d147f0224fdb6..45397921a6651 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/index.test.tsx @@ -14,6 +14,7 @@ import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../mock'; import { createStore, State } from '../../store'; import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers'; @@ -25,6 +26,7 @@ describe('Inspect Button', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); const refetch = jest.fn(); const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); const newQuery: UpdateQueryParams = { inputId: 'global', id: 'myQuery', @@ -34,13 +36,13 @@ describe('Inspect Button', () => { state: state.inputs, }; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); describe('Render', () => { beforeEach(() => { const myState = cloneDeep(state); myState.inputs = upsertQuery(newQuery); - store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); test('Eui Empty Button', () => { const wrapper = mount( @@ -144,7 +146,7 @@ describe('Inspect Button', () => { response: ['my response'], }; myState.inputs = upsertQuery(myQuery); - store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); test('Open Inspect Modal', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx index d81d23438bfd2..50721ef3b26ad 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx @@ -30,7 +30,12 @@ import { mockNoChartMappings, mockNarrowDateRange, } from '../../../network/components/kpi_network/mock'; -import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER } from '../../mock'; +import { + mockGlobalState, + apolloClientObservable, + SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, +} from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { KpiNetworkData, KpiHostsData } from '../../../graphql/types'; @@ -49,7 +54,8 @@ jest.mock('../charts/barchart', () => { describe('Stat Items Component', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); const state: State = mockGlobalState; - const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); describe.each([ [ diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx index 04cb348c3f9cd..99510a1b4b42e 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx @@ -10,7 +10,12 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../../common/constants'; import { useUiSetting$ } from '../../lib/kibana'; -import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../mock'; +import { + apolloClientObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, +} from '../../mock'; import { createUseUiSetting$Mock } from '../../mock/kibana_react'; import { createStore, State } from '../../store'; @@ -75,11 +80,12 @@ const timepickerRanges = [ describe('SIEM Super Date Picker', () => { describe('#SuperDatePicker', () => { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { jest.clearAllMocks(); - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); mockUseUiSetting$.mockImplementation((key, defaultValue) => { const useUiSetting$Mock = createUseUiSetting$Mock(); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index aa310d63ab283..1c24c325dc9ec 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -13,6 +13,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../mock'; import { createKibanaCoreStartMock } from '../../mock/kibana_core'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; @@ -141,7 +142,9 @@ const state: State = { }, }, }; -const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + +const { storage } = createSecuritySolutionStorageMock(); +const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); describe('StatefulTopN', () => { // Suppress warnings about "react-beautiful-dnd" diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts index 759d72419e42e..cb4de29802e54 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts @@ -5,10 +5,13 @@ */ import { MouseEventHandler, useCallback } from 'react'; -import { ApplicationStart } from 'kibana/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { ApplicationStart, NavigateToAppOptions } from 'kibana/public'; +import { useKibana } from '../../lib/kibana'; -type NavigateToAppHandlerProps = Parameters; +type NavigateToAppHandlerOptions = NavigateToAppOptions & { + state?: S; + onClick?: EventHandlerCallback; +}; type EventHandlerCallback = MouseEventHandler; /** @@ -25,14 +28,12 @@ type EventHandlerCallback = MouseEventHandlerSee configs */ -export const useNavigateToAppEventHandler = ( +export const useNavigateToAppEventHandler = ( /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ - appId: NavigateToAppHandlerProps[0], + appId: Parameters[0], /** Options, some of which are passed along to the app route */ - options?: NavigateToAppHandlerProps[1] & { - onClick?: EventHandlerCallback; - } + options?: NavigateToAppHandlerOptions ): EventHandlerCallback => { const { services } = useKibana(); const { path, state, onClick } = options || {}; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index d930136b3c0c4..1db63897a8863 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -19,7 +19,7 @@ import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middlewar import { AppRootProvider } from './app_root_provider'; import { managementMiddlewareFactory } from '../../../management/store/middleware'; import { createKibanaContextProviderMock } from '../kibana_react'; -import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..'; +import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -56,7 +56,9 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { const coreStart = coreMock.createStart({ basePath: '/mock' }); const depsStart = depsStartMock(); const middlewareSpy = createSpyMiddleware(); - const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [ + const { storage } = createSecuritySolutionStorageMock(); + + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage, [ substateMiddlewareFactory( (globalState) => globalState.alertList, alertMiddlewareFactory(coreStart, depsStart) @@ -64,6 +66,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { ...managementMiddlewareFactory(coreStart, depsStart), middlewareSpy.actionSpyMiddleware, ]); + const MockKibanaContextProvider = createKibanaContextProviderMock(); const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( diff --git a/x-pack/plugins/security_solution/public/common/mock/index.ts b/x-pack/plugins/security_solution/public/common/mock/index.ts index 30eb4c63f40b8..678ad4d84b586 100644 --- a/x-pack/plugins/security_solution/public/common/mock/index.ts +++ b/x-pack/plugins/security_solution/public/common/mock/index.ts @@ -10,6 +10,7 @@ export * from './hook_wrapper'; export * from './index_pattern'; export * from './mock_timeline_data'; export * from './mock_detail_item'; +export * from './mock_local_storage'; export * from './netflow'; export * from './test_providers'; export * from './utils'; diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_local_storage.ts b/x-pack/plugins/security_solution/public/common/mock/mock_local_storage.ts new file mode 100644 index 0000000000000..ca44295b75889 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/mock/mock_local_storage.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IStorage, Storage } from '../../../../../../src/plugins/kibana_utils/public'; + +export const localStorageMock = (): IStorage => { + let store: Record = {}; + + return { + getItem: (key: string) => { + return store[key] || null; + }, + setItem: (key: string, value: unknown) => { + store[key] = value; + }, + clear() { + store = {}; + }, + removeItem(key: string) { + delete store[key]; + }, + }; +}; + +export const createSecuritySolutionStorageMock = () => { + const localStorage = localStorageMock(); + return { + localStorage, + storage: new Storage(localStorage), + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 679e0bdc14cd5..0573f049c35c5 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -22,6 +22,7 @@ import { mockGlobalState } from './global_state'; import { createKibanaContextProviderMock } from './kibana_react'; import { FieldHook, useForm } from '../../shared_imports'; import { SUB_PLUGINS_REDUCER } from './utils'; +import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage'; const state: State = mockGlobalState; @@ -38,32 +39,17 @@ export const apolloClient = new ApolloClient({ export const apolloClientObservable = new BehaviorSubject(apolloClient); -const localStorageMock = () => { - let store: Record = {}; - - return { - getItem: (key: string) => { - return store[key] || null; - }, - setItem: (key: string, value: unknown) => { - store[key] = value; - }, - clear() { - store = {}; - }, - }; -}; - Object.defineProperty(window, 'localStorage', { value: localStorageMock(), }); const MockKibanaContextProvider = createKibanaContextProviderMock(); +const { storage } = createSecuritySolutionStorageMock(); /** A utility for wrapping children in the providers required to run most tests */ const TestProvidersComponent: React.FC = ({ children, - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable), + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage), onDragEnd = jest.fn(), }) => ( @@ -83,7 +69,7 @@ export const TestProviders = React.memo(TestProvidersComponent); const TestProviderWithoutDragAndDropComponent: React.FC = ({ children, - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable), + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage), }) => ( {children} diff --git a/x-pack/plugins/security_solution/public/common/store/epic.ts b/x-pack/plugins/security_solution/public/common/store/epic.ts index b9e8e7d88c202..d9de7951a86f4 100644 --- a/x-pack/plugins/security_solution/public/common/store/epic.ts +++ b/x-pack/plugins/security_solution/public/common/store/epic.ts @@ -9,11 +9,13 @@ import { createTimelineEpic } from '../../timelines/store/timeline/epic'; import { createTimelineFavoriteEpic } from '../../timelines/store/timeline/epic_favorite'; import { createTimelineNoteEpic } from '../../timelines/store/timeline/epic_note'; import { createTimelinePinnedEventEpic } from '../../timelines/store/timeline/epic_pinned_event'; +import { createTimelineLocalStorageEpic } from '../../timelines/store/timeline/epic_local_storage'; export const createRootEpic = () => combineEpics( createTimelineEpic(), createTimelineFavoriteEpic(), createTimelineNoteEpic(), - createTimelinePinnedEventEpic() + createTimelinePinnedEventEpic(), + createTimelineLocalStorageEpic() ); diff --git a/x-pack/plugins/security_solution/public/common/store/store.ts b/x-pack/plugins/security_solution/public/common/store/store.ts index 276dcdcaedb85..5f53724b287df 100644 --- a/x-pack/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/plugins/security_solution/public/common/store/store.ts @@ -28,6 +28,7 @@ import { AppApolloClient } from '../lib/lib'; import { AppAction } from './actions'; import { Immutable } from '../../../common/endpoint/types'; import { State } from './types'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; type ComposeType = typeof compose; declare global { @@ -48,6 +49,7 @@ export const createStore = ( state: PreloadedState, pluginsReducer: SubPluginsInitReducer, apolloClient: Observable, + storage: Storage, additionalMiddleware?: Array>>> ): Store => { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; @@ -58,6 +60,7 @@ export const createStore = ( selectNotesByIdSelector: appSelectors.selectNotesByIdSelector, timelineByIdSelector: timelineSelectors.timelineByIdSelector, timelineTimeRangeSelector: inputsSelectors.timelineTimeRangeSelector, + storage, }; const epicMiddleware = createEpicMiddleware( diff --git a/x-pack/plugins/security_solution/public/endpoint_alerts/view/test_helpers/render_alert_page.tsx b/x-pack/plugins/security_solution/public/endpoint_alerts/view/test_helpers/render_alert_page.tsx index 11ddc1f252972..acfe3f228c21f 100644 --- a/x-pack/plugins/security_solution/public/endpoint_alerts/view/test_helpers/render_alert_page.tsx +++ b/x-pack/plugins/security_solution/public/endpoint_alerts/view/test_helpers/render_alert_page.tsx @@ -15,7 +15,12 @@ import { AlertIndex } from '../index'; import { RouteCapture } from '../../../common/components/endpoint/route_capture'; import { depsStartMock } from '../../../common/mock/endpoint'; import { createStore } from '../../../common/store'; -import { SUB_PLUGINS_REDUCER, mockGlobalState, apolloClientObservable } from '../../../common/mock'; +import { + SUB_PLUGINS_REDUCER, + mockGlobalState, + apolloClientObservable, + createSecuritySolutionStorageMock, +} from '../../../common/mock'; export const alertPageTestRender = () => { /** @@ -25,7 +30,8 @@ export const alertPageTestRender = () => { /** * Create a store, with the middleware disabled. We don't want side effects being created by our code in this test. */ - const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const depsStart = depsStartMock(); depsStart.data.ui.SearchBar.mockImplementation(() =>
); diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx index a38b25661cd5e..3809d848759cc 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.test.tsx @@ -9,7 +9,12 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../../common/mock'; +import { + apolloClientObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, +} from '../../../common/mock'; import { createStore, State } from '../../../common/store'; import { hostsModel } from '../../store'; import { mockData } from './mock'; @@ -20,10 +25,11 @@ describe('Authentication Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx index 45779bf37c77f..1168f4f7454c8 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx @@ -15,6 +15,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -35,12 +36,13 @@ jest.mock('../../../common/components/query_bar', () => ({ describe('Hosts Table', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts index 6f27428e71c27..90d5f54a027d7 100644 --- a/x-pack/plugins/security_solution/public/hosts/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/index.ts @@ -4,16 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline'; import { SecuritySubPluginWithStore } from '../app/types'; +import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; import { getHostsRoutes } from './routes'; import { initialHostsState, hostsReducer, HostsState } from './store'; +const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, +]; + export class Hosts { public setup() {} - public start(): SecuritySubPluginWithStore<'hosts', HostsState> { + public start(storage: Storage): SecuritySubPluginWithStore<'hosts', HostsState> { return { routes: getHostsRoutes(), + storageTimelines: { + timelineById: getTimelinesInStorageByIds(storage, HOST_TIMELINE_IDS), + }, store: { initialState: { hosts: initialHostsState }, reducer: { hosts: hostsReducer }, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index d2ccbd76fac10..85db3b4e159f1 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -19,6 +19,7 @@ import { TestProviders, mockGlobalState, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../common/mock'; import { SiemNavigation } from '../../common/components/navigation'; import { inputsActions } from '../../common/store/inputs'; @@ -171,7 +172,8 @@ describe('Hosts - rendering', () => { ]; localSource[0].result.data.source.status.indicesExist = true; const myState: State = mockGlobalState; - const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx index a0d8df6b87514..3023670f8051a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/alerts_query_tab_body.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { Filter } from '../../../../../../../src/plugins/data/public'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsView } from '../../../common/components/alerts_viewer'; import { AlertsComponentQueryProps } from './types'; @@ -48,7 +49,13 @@ export const HostAlertsQueryTabBody = React.memo((alertsProps: AlertsComponentQu [pageFilters] ); - return ; + return ( + + ); }); HostAlertsQueryTabBody.displayName = 'HostAlertsQueryTabBody'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index b8ec269508442..574e2ec4ae250 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -5,6 +5,7 @@ */ import React, { useEffect } from 'react'; +import { TimelineId } from '../../../../common/types/timeline'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HostsComponentsQueryProps } from './types'; import { hostsModel } from '../../store'; @@ -17,7 +18,6 @@ import { MatrixHistogramContainer } from '../../../common/components/matrix_hist import * as i18n from '../translations'; import { HistogramType } from '../../../graphql/types'; -const HOSTS_PAGE_TIMELINE_ID = 'hosts-page'; const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; export const eventsStackByOptions: MatrixHistogramOption[] = [ @@ -78,7 +78,7 @@ export const EventsQueryTabBody = ({ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index b191bfe4effea..9ec65a5d17898 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -93,13 +93,40 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath); + const [policyDetailsRoutePath, policyDetailsRouteUrl] = useMemo(() => { + return [ + getManagementUrl({ + name: 'policyDetails', + policyId: details.endpoint.policy.applied.id, + excludePrefix: true, + }), + getManagementUrl({ + name: 'policyDetails', + policyId: details.endpoint.policy.applied.id, + }), + ]; + }, [details.endpoint.policy.applied.id]); + + const policyDetailsClickHandler = useNavigateByRouterEventHandler(policyDetailsRoutePath); + const detailsResultsPolicy = useMemo(() => { return [ { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policy', { defaultMessage: 'Policy', }), - description: details.endpoint.policy.applied.id, + description: ( + <> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {details.endpoint.policy.applied.name} + + + ), }, { title: i18n.translate('xpack.securitySolution.endpoint.host.details.policyStatus', { @@ -128,7 +155,14 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { ), }, ]; - }, [details, policyResponseUri, policyStatus, policyStatusClickHandler]); + }, [ + details, + policyResponseUri, + policyStatus, + policyStatusClickHandler, + policyDetailsRouteUrl, + policyDetailsClickHandler, + ]); const detailsResultsLower = useMemo(() => { return [ { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts index 645a4896770ee..790bbd3cb98da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { HostStatus, HostPolicyResponseActionStatus } from '../../../../../common/endpoint/types'; export const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze< @@ -23,3 +24,17 @@ export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< warning: 'warning', failure: 'danger', }); + +export const POLICY_STATUS_TO_TEXT = Object.freeze< + { [key in keyof typeof HostPolicyResponseActionStatus]: string } +>({ + success: i18n.translate('xpack.securitySolution.policyStatusText.success', { + defaultMessage: 'Success', + }), + warning: i18n.translate('xpack.securitySolution.policyStatusText.warning', { + defaultMessage: 'Warning', + }), + failure: i18n.translate('xpack.securitySolution.policyStatusText.failure', { + defaultMessage: 'Failure', + }), +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 7d84bb52238a2..e0f797b143055 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -9,6 +9,7 @@ import * as reactTestingLibrary from '@testing-library/react'; import { HostList } from './index'; import { mockHostDetailsApiResult, mockHostResultList } from '../store/mock_host_result_list'; +import { mockPolicyResultList } from '../../policy/store/policy_list/mock_policy_result_list'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; import { HostInfo, @@ -17,6 +18,7 @@ import { } from '../../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { AppAction } from '../../../../common/store/actions'; +import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants'; describe('when on the hosts page', () => { const docGenerator = new EndpointDocGenerator(); @@ -47,15 +49,23 @@ describe('when on the hosts page', () => { }); }); describe('when list data loads', () => { + const generatedPolicyStatuses: Array< + HostInfo['metadata']['endpoint']['policy']['applied']['status'] + > = []; + let firstPolicyID: string; beforeEach(() => { reactTestingLibrary.act(() => { const hostListData = mockHostResultList({ total: 3 }); + firstPolicyID = hostListData.hosts[0].metadata.endpoint.policy.applied.id; [HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE].forEach((status, index) => { hostListData.hosts[index] = { metadata: hostListData.hosts[index].metadata, host_status: status, }; }); + hostListData.hosts.forEach((item, index) => { + generatedPolicyStatuses[index] = item.metadata.endpoint.policy.applied.status; + }); const action: AppAction = { type: 'serverReturnedHostList', payload: hostListData, @@ -92,6 +102,29 @@ describe('when on the hosts page', () => { ).not.toBeNull(); }); + it('should display correct policy status', async () => { + const renderResult = render(); + const policyStatuses = await renderResult.findAllByTestId('rowPolicyStatus'); + + policyStatuses.forEach((status, index) => { + expect(status.textContent).toEqual(POLICY_STATUS_TO_TEXT[generatedPolicyStatuses[index]]); + expect( + status.querySelector( + `[data-euiicon-type][color=${ + POLICY_STATUS_TO_HEALTH_COLOR[generatedPolicyStatuses[index]] + }]` + ) + ).not.toBeNull(); + }); + }); + + it('should display policy name as a link', async () => { + const renderResult = render(); + const firstPolicyName = (await renderResult.findAllByTestId('policyNameCellLink'))[0]; + expect(firstPolicyName).not.toBeNull(); + expect(firstPolicyName.getAttribute('href')).toContain(`policy/${firstPolicyID}`); + }); + describe('when the user clicks the first hostname in the table', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { @@ -197,6 +230,32 @@ describe('when on the hosts page', () => { expect(flyout).not.toBeNull(); }); }); + + it('should display policy name value as a link', async () => { + const renderResult = render(); + const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); + expect(policyDetailsLink).not.toBeNull(); + expect(policyDetailsLink.getAttribute('href')).toEqual( + `#/management/policy/${hostDetails.metadata.endpoint.policy.applied.id}` + ); + }); + + it('should update the URL when policy name link is clicked', async () => { + const policyItem = mockPolicyResultList({ total: 1 }).items[0]; + coreStart.http.get.mockReturnValue(Promise.resolve({ item: policyItem })); + + const renderResult = render(); + const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); + const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(policyDetailsLink); + }); + const changedUrlAction = await userChangedUrlChecker; + expect(changedUrlAction.payload.pathname).toEqual( + `/management/policy/${hostDetails.metadata.endpoint.policy.applied.id}` + ); + }); + it('should display policy status value as a link', async () => { const renderResult = render(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); @@ -205,6 +264,7 @@ describe('when on the hosts page', () => { '#/management/endpoints?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); + it('should update the URL when policy status link is clicked', async () => { const renderResult = render(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); @@ -217,6 +277,7 @@ describe('when on the hosts page', () => { '?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); + it('should display Success overall policy status', async () => { const renderResult = render(); reactTestingLibrary.act(() => { @@ -230,6 +291,7 @@ describe('when on the hosts page', () => { policyStatusHealth.querySelector('[data-euiicon-type][color="success"]') ).not.toBeNull(); }); + it('should display Warning overall policy status', async () => { const renderResult = render(); reactTestingLibrary.act(() => { @@ -243,6 +305,7 @@ describe('when on the hosts page', () => { policyStatusHealth.querySelector('[data-euiicon-type][color="warning"]') ).not.toBeNull(); }); + it('should display Failed overall policy status', async () => { const renderResult = render(); reactTestingLibrary.act(() => { @@ -256,6 +319,7 @@ describe('when on the hosts page', () => { policyStatusHealth.querySelector('[data-euiicon-type][color="danger"]') ).not.toBeNull(); }); + it('should display Unknown overall policy status', async () => { const renderResult = render(); reactTestingLibrary.act(() => { @@ -269,6 +333,7 @@ describe('when on the hosts page', () => { policyStatusHealth.querySelector('[data-euiicon-type][color="subdued"]') ).not.toBeNull(); }); + it('should include the link to logs', async () => { const renderResult = render(); const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); @@ -278,6 +343,7 @@ describe('when on the hosts page', () => { "/app/logs/stream?logFilter=(expression:'host.id:1',kind:kuery)" ); }); + describe('when link to logs is clicked', () => { beforeEach(async () => { const renderResult = render(); @@ -291,6 +357,7 @@ describe('when on the hosts page', () => { expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); + describe('when showing host Policy Response panel', () => { let renderResult: ReturnType; beforeEach(async () => { @@ -305,10 +372,12 @@ describe('when on the hosts page', () => { dispatchServerReturnedHostPolicyResponse(); }); }); + it('should hide the host details panel', async () => { const hostDetailsFlyout = await renderResult.queryByTestId('hostDetailsFlyoutBody'); expect(hostDetailsFlyout).toBeNull(); }); + it('should display policy response sub-panel', async () => { expect( await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutHeader') @@ -317,17 +386,20 @@ describe('when on the hosts page', () => { await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutBody') ).not.toBeNull(); }); + it('should include the sub-panel title', async () => { expect( (await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutTitle')).textContent ).toBe('Policy Response'); }); + it('should show a configuration section for each protection', async () => { const configAccordions = await renderResult.findAllByTestId( 'hostDetailsPolicyResponseConfigAccordion' ); expect(configAccordions).not.toBeNull(); }); + it('should show an actions section for each configuration', async () => { const actionAccordions = await renderResult.findAllByTestId( 'hostDetailsPolicyResponseActionsAccordion' @@ -340,6 +412,7 @@ describe('when on the hosts page', () => { expect(statusHealth).not.toBeNull(); expect(message).not.toBeNull(); }); + it('should not show any numbered badges if all actions are successful', () => { const policyResponse = docGenerator.generatePolicyResponse( new Date().getTime(), @@ -359,6 +432,7 @@ describe('when on the hosts page', () => { expect(e).not.toBeNull(); }); }); + it('should show a numbered badge if at least one action failed', () => { reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure); @@ -368,6 +442,7 @@ describe('when on the hosts page', () => { ); expect(attentionBadge).not.toBeNull(); }); + it('should show a numbered badge if at least one action has a warning', () => { reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning); @@ -377,6 +452,7 @@ describe('when on the hosts page', () => { ); expect(attentionBadge).not.toBeNull(); }); + it('should include the back to details link', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); @@ -384,6 +460,7 @@ describe('when on the hosts page', () => { '#/management/endpoints?page_index=0&page_size=10&selected_host=1' ); }); + it('should update URL when back to details link is clicked', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 125723e9bcea6..c67c29fbc73a9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -22,7 +22,11 @@ import { createStructuredSelector } from 'reselect'; import { HostDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; import { useHostSelector } from './hooks'; -import { HOST_STATUS_TO_HEALTH_COLOR } from './host_constants'; +import { + HOST_STATUS_TO_HEALTH_COLOR, + POLICY_STATUS_TO_HEALTH_COLOR, + POLICY_STATUS_TO_TEXT, +} from './host_constants'; import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { CreateStructuredSelector } from '../../../../common/store'; import { Immutable, HostInfo } from '../../../../../common/endpoint/types'; @@ -31,17 +35,18 @@ import { ManagementPageView } from '../../../components/management_page_view'; import { getManagementUrl } from '../../..'; import { FormattedDate } from '../../../../common/components/formatted_date'; -const HostLink = memo<{ +const HostListNavLink = memo<{ name: string; href: string; route: string; -}>(({ name, href, route }) => { + dataTestSubj: string; +}>(({ name, href, route, dataTestSubj }) => { const clickHandler = useNavigateByRouterEventHandler(route); return ( // eslint-disable-next-line @elastic/eui/href-or-on-click ); }); -HostLink.displayName = 'HostLink'; +HostListNavLink.displayName = 'HostListNavLink'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { @@ -116,7 +121,14 @@ export const HostList = () => { name: 'endpointDetails', selected_host: id, }); - return ; + return ( + + ); }, }, { @@ -142,43 +154,64 @@ export const HostList = () => { }, }, { - field: '', + field: 'metadata.endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policy', { defaultMessage: 'Policy', }), truncateText: true, // eslint-disable-next-line react/display-name - render: () => { - return {'Policy Name'}; + render: (policy: HostInfo['metadata']['endpoint']['policy']['applied']) => { + const toRoutePath = getManagementUrl({ + name: 'policyDetails', + policyId: policy.id, + excludePrefix: true, + }); + const toRouteUrl = getManagementUrl({ + name: 'policyDetails', + policyId: policy.id, + }); + return ( + + ); }, }, { - field: '', + field: 'metadata.endpoint.policy.applied', name: i18n.translate('xpack.securitySolution.endpointList.policyStatus', { defaultMessage: 'Policy Status', }), // eslint-disable-next-line react/display-name - render: () => { + render: (policy: HostInfo['metadata']['endpoint']['policy']['applied'], item: HostInfo) => { + const toRoutePath = getManagementUrl({ + name: 'endpointPolicyResponse', + selected_host: item.metadata.host.id, + excludePrefix: true, + }); + const toRouteUrl = getManagementUrl({ + name: 'endpointPolicyResponse', + selected_host: item.metadata.host.id, + }); return ( - - + ); }, }, - { - field: '', - name: i18n.translate('xpack.securitySolution.endpointList.alerts', { - defaultMessage: 'Alerts', - }), - dataType: 'number', - render: () => { - return '0'; - }, - }, { field: 'metadata.host.os.name', name: i18n.translate('xpack.securitySolution.endpointList.os', { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts index cece3f1b4c8f2..66e98aa51601e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts @@ -11,6 +11,7 @@ import { DeleteDatasourcesResponse, DeleteDatasourcesRequest, DATASOURCE_SAVED_OBJECT_TYPE, + GetPackagesResponse, } from '../../../../../../../../ingest_manager/common'; import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; import { NewPolicyData } from '../../../../../../../common/endpoint/types'; @@ -19,6 +20,7 @@ const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`; const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; +const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`; const INGEST_API_DELETE_DATASOURCE = `${INGEST_API_DATASOURCES}/delete`; /** @@ -113,3 +115,20 @@ export const sendGetFleetAgentStatusForConfig = ( }, }); }; + +/** + * Get Endpoint Security Package information + */ +export const sendGetEndpointSecurityPackage = async ( + http: HttpStart +): Promise => { + const options = { query: { category: 'security' } }; + const securityPackages = await http.get(INGEST_API_EPM_PACKAGES, options); + const endpointPackageInfo = securityPackages.response.find( + (epmPackage) => epmPackage.name === 'endpoint' + ); + if (!endpointPackageInfo) { + throw new Error('Endpoint package was not found.'); + } + return endpointPackageInfo; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_hooks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_hooks.ts new file mode 100644 index 0000000000000..75e1556ff0bb0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_hooks.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 { useEffect, useState } from 'react'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { GetPackagesResponse } from '../../../../../../ingest_manager/common/types/rest_spec'; +import { sendGetEndpointSecurityPackage } from '../store/policy_list/services/ingest'; +import { useKibana } from '../../../../common/lib/kibana'; + +type UseEndpointPackageInfo = [ + /** The Package Info. will be undefined while it is being fetched */ + Immutable | undefined, + /** Boolean indicating if fetching is underway */ + boolean, + /** Any error encountered during fetch */ + Error | undefined +]; + +/** + * Hook that fetches the endpoint package info + * + * @example + * const [packageInfo, isFetching, fetchError] = useEndpointPackageInfo(); + */ +export const useEndpointPackageInfo = (): UseEndpointPackageInfo => { + const { + services: { http }, + } = useKibana(); + const [endpointPackage, setEndpointPackage] = useState(); + const [isFetching, setIsFetching] = useState(true); + const [error, setError] = useState(); + + useEffect(() => { + sendGetEndpointSecurityPackage(http) + .then((packageInfo) => setEndpointPackage(packageInfo)) + .catch((apiError) => setError(apiError)) + .finally(() => setIsFetching(false)); + }, [http]); + + return [endpointPackage, isFetching, error]; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx index e80856f35081b..5e721cadfa823 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_datasource.tsx @@ -32,6 +32,7 @@ export const ConfigureEndpointDatasource = memo

diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 24254530f75db..090a16a763664 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -21,6 +21,7 @@ import { EuiConfirmModal, EuiCallOut, EuiSpacer, + EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -40,6 +41,9 @@ import { ManagementPageView } from '../../../components/management_page_view'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { getManagementUrl } from '../../../common/routing'; import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time'; +import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; +import { CreateDatasourceRouteState } from '../../../../../../ingest_manager/public'; +import { useEndpointPackageInfo } from './ingest_hooks'; interface TableChangeCallbackArguments { page: { index: number; size: number }; @@ -121,6 +125,7 @@ export const PolicyList = React.memo(() => { const [policyIdToDelete, setPolicyIdToDelete] = useState(''); const dispatch = useDispatch<(action: PolicyListAction) => void>(); + const [packageInfo, isFetchingPackageInfo] = useEndpointPackageInfo(); const { selectPolicyItems: policyItems, selectPageIndex: pageIndex, @@ -133,6 +138,28 @@ export const PolicyList = React.memo(() => { selectAgentStatusSummary: agentStatusSummary, } = usePolicyListSelector(selector); + const handleCreatePolicyClick = useNavigateToAppEventHandler( + 'ingestManager', + { + // We redirect to Ingest's Integaration page if we can't get the package version, and + // to the Integration Endpoint Package Add Datasource if we have package information. + // Also, + // We pass along soem state information so that the Ingest page can change the behaviour + // of the cancel and submit buttons and redirect the user back to endpoint policy + path: `#/integrations${packageInfo ? `/endpoint-${packageInfo.version}/add-datasource` : ''}`, + state: { + onCancelNavigateTo: [ + 'securitySolution', + { path: getManagementUrl({ name: 'policyList' }) }, + ], + onCancelUrl: services.application?.getUrlForApp('securitySolution', { + path: getManagementUrl({ name: 'policyList' }), + }), + onSaveNavigateTo: ['securitySolution', { path: getManagementUrl({ name: 'policyList' }) }], + }, + } + ); + useEffect(() => { if (apiError) { notifications.toasts.danger({ @@ -369,6 +396,19 @@ export const PolicyList = React.memo(() => { headerLeft={i18n.translate('xpack.securitySolution.endpoint.policyList.viewTitle', { defaultMessage: 'Policies', })} + headerRight={ + + + + } bodyHeader={ { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx index 70c952b110745..580a5420f1c34 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx @@ -8,7 +8,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../../common/mock'; +import { + apolloClientObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, +} from '../../../common/mock'; import { createStore, State } from '../../../common/store'; import { KpiNetworkComponent } from '.'; import { mockData } from './mock'; @@ -19,10 +24,11 @@ describe('KpiNetwork Component', () => { const to = new Date('2019-06-18T06:00:00.000Z').valueOf(); const narrowDateRange = jest.fn(); - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx index 25449214b6e77..036ebedd6b88e 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_dns_table/index.test.tsx @@ -15,6 +15,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { State, createStore } from '../../../common/store'; import { networkModel } from '../../store'; @@ -26,11 +27,12 @@ import { mockData } from './mock'; describe('NetworkTopNFlow Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx index e9020421a411e..39fad58ca3528 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_http_table/index.test.tsx @@ -15,6 +15,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -27,11 +28,12 @@ describe('NetworkHttp Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx index 8552d3184fcc2..8b1dbc8c558b6 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.test.tsx @@ -17,6 +17,7 @@ import { mockIndexPattern, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -30,10 +31,11 @@ describe('NetworkTopCountries Table Component', () => { const state: State = mockGlobalState; const mount = useMountAppended(); - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx index e40bbd40f4cd2..4de04f673d879 100644 --- a/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/network_top_n_flow_table/index.test.tsx @@ -16,6 +16,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -27,11 +28,12 @@ describe('NetworkTopNFlow Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx index ffb68f4df8202..acbe974f914d7 100644 --- a/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/tls_table/index.test.tsx @@ -15,6 +15,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -26,11 +27,12 @@ describe('Tls Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('Rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx index 981e182154c5e..f0d4d7fbeefc6 100644 --- a/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/users_table/index.test.tsx @@ -16,6 +16,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -28,11 +29,12 @@ describe('Users Table Component', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mount = useMountAppended(); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); describe('Rendering', () => { diff --git a/x-pack/plugins/security_solution/public/network/index.ts b/x-pack/plugins/security_solution/public/network/index.ts index 6590e5ee5161c..63291ad2d2396 100644 --- a/x-pack/plugins/security_solution/public/network/index.ts +++ b/x-pack/plugins/security_solution/public/network/index.ts @@ -4,16 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { SecuritySubPluginWithStore } from '../app/types'; import { getNetworkRoutes } from './routes'; import { initialNetworkState, networkReducer, NetworkState } from './store'; +import { TimelineId } from '../../common/types/timeline'; +import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; export class Network { public setup() {} - public start(): SecuritySubPluginWithStore<'network', NetworkState> { + public start(storage: Storage): SecuritySubPluginWithStore<'network', NetworkState> { return { routes: getNetworkRoutes(), + storageTimelines: { + timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]), + }, store: { initialState: { network: initialNetworkState }, reducer: { network: networkReducer }, diff --git a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx index 22a5d7af88eb8..bbb964ae17b9f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/ip_details/index.test.tsx @@ -20,6 +20,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; import { createStore, State } from '../../../common/store'; @@ -118,10 +119,11 @@ describe('Ip Details', () => { }); const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); localSource = cloneDeep(mocksSource); }); diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx index c5f59e751ca9a..0c9f8c194bf2b 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/alerts_query_tab_body.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; +import { TimelineId } from '../../../../common/types/timeline'; import { AlertsView } from '../../../common/components/alerts_viewer'; import { NetworkComponentQueryProps } from './types'; @@ -62,7 +63,11 @@ export const filterNetworkData: Filter[] = [ ]; export const NetworkAlertsQueryTabBody = React.memo((alertsProps: NetworkComponentQueryProps) => ( - + )); NetworkAlertsQueryTabBody.displayName = 'NetworkAlertsQueryTabBody'; diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx index 23948209fccfe..e1078dee3eb0d 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx @@ -18,6 +18,7 @@ import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../common/mock'; import { State, createStore } from '../../common/store'; import { inputsActions } from '../../common/store/inputs'; @@ -155,7 +156,8 @@ describe('rendering - rendering', () => { ]; localSource[0].result.data.source.status.indicesExist = true; const myState: State = mockGlobalState; - const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx index 49347ab810547..8b04c329731ab 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.test.tsx @@ -14,6 +14,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { OverviewHost } from '.'; @@ -92,11 +93,12 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ describe('OverviewHost', () => { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { const myState = cloneDeep(state); - store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); test('it renders the expected widget title', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index 4451135c608ce..2fcf6f83f0ae0 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -13,6 +13,7 @@ import { mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../common/mock'; import { OverviewNetwork } from '.'; @@ -83,11 +84,12 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [ describe('OverviewNetwork', () => { const state: State = mockGlobalState; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { const myState = cloneDeep(state); - store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); }); test('it renders the expected widget title', () => { diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index e3d3062ee9cf7..b9eef9f799d3b 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -14,6 +14,7 @@ import { Plugin as IPlugin, DEFAULT_APP_CATEGORIES, } from '../../../../src/core/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import { initTelemetry } from './common/lib/telemetry'; import { KibanaServices } from './common/lib/kibana/services'; @@ -50,12 +51,14 @@ export class Plugin implements IPlugin { + const storage = new Storage(localStorage); const [coreStart, startPlugins] = await core.getStartServices(); const { renderApp } = await import('./app'); const services = { ...coreStart, ...startPlugins, security: plugins.security, + storage, } as StartServices; const alertsSubPlugin = new (await import('./alerts')).Alerts(); @@ -67,15 +70,27 @@ export class Plugin implements IPlugin { const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); describe('rendering', () => { test('it renders correctly against snapshot', () => { @@ -59,7 +61,8 @@ describe('Flyout', () => { const storeShowIsTrue = createStore( stateShowIsTrue, SUB_PLUGINS_REDUCER, - apolloClientObservable + apolloClientObservable, + storage ); const wrapper = mount( @@ -82,7 +85,8 @@ describe('Flyout', () => { const storeWithDataProviders = createStore( stateWithDataProviders, SUB_PLUGINS_REDUCER, - apolloClientObservable + apolloClientObservable, + storage ); const wrapper = mount( @@ -103,7 +107,8 @@ describe('Flyout', () => { const storeWithDataProviders = createStore( stateWithDataProviders, SUB_PLUGINS_REDUCER, - apolloClientObservable + apolloClientObservable, + storage ); const wrapper = mount( @@ -136,7 +141,8 @@ describe('Flyout', () => { const storeWithDataProviders = createStore( stateWithDataProviders, SUB_PLUGINS_REDUCER, - apolloClientObservable + apolloClientObservable, + storage ); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx index 02ebfd7f752b5..3352639fa95f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/actions/index.tsx @@ -48,7 +48,7 @@ export const Actions = React.memo(({ header, onColumnRemoved, sort, isLoa <> {sort.columnId === header.id && isLoading ? ( - + ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap index fcf0f1019e67a..f155b379227f0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/__snapshots__/index.test.tsx.snap @@ -33,24 +33,28 @@ exports[`Footer Timeline Component rendering it renders the default timeline foo items={ Array [ 1 rows , 5 rows , 10 rows , diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx index 594bf6d43e285..4e1595eef984c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx @@ -144,6 +144,7 @@ export const EventsCountComponent = ({ iconType="arrowDown" iconSide="right" onClick={onClick} + data-test-subj="local-events-count-button" /> {` ${i18n.OF} `} @@ -289,6 +290,7 @@ export const FooterComponent = ({ { closePopover(); onChangeItemsPerPage(item); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx index 660beffd9a089..c67efe204eb0e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx @@ -11,6 +11,7 @@ import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, TestProviders, } from '../../../../common/mock'; import { createStore, State } from '../../../../common/store'; @@ -97,12 +98,14 @@ const defaultProps = { }; describe('Properties', () => { const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); let mockedWidth = 1000; - let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); beforeEach(() => { jest.clearAllMocks(); - store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); (useThrottledResizeObserver as jest.Mock).mockReturnValue({ width: mockedWidth }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.test.tsx index 879302d4a92c1..cd6233334c5de 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/new_template_timeline.test.tsx @@ -11,6 +11,7 @@ import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER, + createSecuritySolutionStorageMock, } from '../../../../common/mock'; import { createStore, State } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; @@ -24,7 +25,8 @@ jest.mock('../../../../common/lib/kibana', () => { describe('NewTemplateTimeline', () => { const state: State = mockGlobalState; - const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable); + const { storage } = createSecuritySolutionStorageMock(); + const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); const mockClosePopover = jest.fn(); const mockTitle = 'NEW_TIMELINE'; let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 7363a60974275..6a6d74cc91508 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -11,6 +11,7 @@ import { Query } from 'react-apollo'; import { compose, Dispatch } from 'redux'; import { connect, ConnectedProps } from 'react-redux'; +import { TimelineId } from '../../../common/types/timeline'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; import { @@ -27,7 +28,8 @@ import { QueryTemplate, QueryTemplateProps } from '../../common/containers/query import { EventType } from '../../timelines/store/timeline/model'; import { timelineQuery } from './index.gql_query'; import { timelineActions } from '../../timelines/store/timeline'; -import { ALERTS_TABLE_TIMELINE_ID } from '../../alerts/components/alerts_table'; + +const timelineIds = [TimelineId.alertsPage, TimelineId.alertsRulesDetailsPage]; export interface TimelineArgs { events: TimelineItem[]; @@ -182,7 +184,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => ({ clearSignalsState: ({ id }: { id?: string }) => { - if (id != null && id === ALERTS_TABLE_TIMELINE_ID) { + if (id != null && timelineIds.some((timelineId) => timelineId === id)) { dispatch(timelineActions.clearEventsLoading({ id })); dispatch(timelineActions.clearEventsDeleted({ id })); } diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts new file mode 100644 index 0000000000000..e1bccbdff4889 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + LOCAL_STORAGE_TIMELINE_KEY, + useTimelinesStorage, + getTimelinesInStorageByIds, + getAllTimelinesInStorage, + addTimelineInStorage, +} from '.'; + +import { TimelineId } from '../../../../common/types/timeline'; +import { mockTimelineModel, createSecuritySolutionStorageMock } from '../../../common/mock'; +import { useKibana } from '../../../common/lib/kibana'; +import { createUseKibanaMock } from '../../../common/mock/kibana_react'; + +jest.mock('../../../common/lib/kibana'); + +const useKibanaMock = useKibana as jest.Mock; + +describe('SiemLocalStorage', () => { + const { localStorage, storage } = createSecuritySolutionStorageMock(); + + beforeEach(() => { + jest.resetAllMocks(); + useKibanaMock.mockImplementation(() => ({ + services: { + ...createUseKibanaMock()().services, + storage, + }, + })); + localStorage.clear(); + }); + + describe('addTimeline', () => { + it('adds a timeline when storage is empty', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + }); + }); + + it('adds a timeline when storage contains another timelines', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + }); + }); + }); + + describe('getAllTimelines', () => { + it('gets all timelines correctly', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = timelineStorage.getAllTimelines(); + expect(timelines).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + }); + }); + + it('returns an empty object if there is no timelines', () => { + const timelineStorage = useTimelinesStorage(); + const timelines = timelineStorage.getAllTimelines(); + expect(timelines).toEqual({}); + }); + }); + + describe('getTimelineById', () => { + it('gets a timeline by id', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timeline = timelineStorage.getTimelineById(TimelineId.hostsPageEvents); + expect(timeline).toEqual(mockTimelineModel); + }); + }); + + describe('getTimelinesInStorageByIds', () => { + it('gets timelines correctly', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + expect(timelines).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + }); + }); + + it('gets an empty timelime when there is no timelines', () => { + const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageEvents]); + expect(timelines).toEqual({}); + }); + + it('returns empty timelime when there is no ids', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, []); + expect(timelines).toEqual({}); + }); + + it('returns empty timelime when a specific timeline does not exists', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageExternalAlerts]); + expect(timelines).toEqual({}); + }); + + it('returns timelines correctly when one exist and another not', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + expect(timelines).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + }); + }); + }); + + describe('getAllTimelinesInStorage', () => { + it('gets timelines correctly', () => { + const timelineStorage = useTimelinesStorage(); + timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getAllTimelinesInStorage(storage); + expect(timelines).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + }); + }); + + it('gets an empty timeline when there is no timelines', () => { + const timelines = getAllTimelinesInStorage(storage); + expect(timelines).toEqual({}); + }); + }); + + describe('addTimelineInStorage', () => { + it('adds a timeline when storage is empty', () => { + addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); + expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + }); + }); + + it('adds a timeline when storage contains another timelines', () => { + addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel); + addTimelineInStorage(storage, TimelineId.hostsPageExternalAlerts, mockTimelineModel); + expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({ + [TimelineId.hostsPageEvents]: mockTimelineModel, + [TimelineId.hostsPageExternalAlerts]: mockTimelineModel, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx new file mode 100644 index 0000000000000..1a09868da7771 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.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 { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; +import { TimelinesStorage } from './types'; +import { useKibana } from '../../../common/lib/kibana'; +import { TimelineModel } from '../../store/timeline/model'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; + +export const LOCAL_STORAGE_TIMELINE_KEY = 'timelines'; +const EMPTY_TIMELINE = {} as { + [K in TimelineIdLiteral]: TimelineModel; +}; + +export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: TimelineIdLiteral[]) => { + const allTimelines = storage.get(LOCAL_STORAGE_TIMELINE_KEY); + + if (!allTimelines) { + return EMPTY_TIMELINE; + } + + return timelineIds.reduce((acc, timelineId) => { + const timelineModel = allTimelines[timelineId]; + if (!timelineModel) { + return { + ...acc, + }; + } + + return { + ...acc, + [timelineId]: timelineModel, + }; + }, {} as { [K in TimelineIdLiteral]: TimelineModel }); +}; + +export const getAllTimelinesInStorage = (storage: Storage) => + storage.get(LOCAL_STORAGE_TIMELINE_KEY) ?? {}; + +export const addTimelineInStorage = ( + storage: Storage, + id: TimelineIdLiteral, + timeline: TimelineModel +) => { + const timelines = getAllTimelinesInStorage(storage); + storage.set(LOCAL_STORAGE_TIMELINE_KEY, { + ...timelines, + [id]: timeline, + }); +}; + +export const useTimelinesStorage = (): TimelinesStorage => { + const { storage } = useKibana().services; + + const getAllTimelines: TimelinesStorage['getAllTimelines'] = () => + getAllTimelinesInStorage(storage); + + const getTimelineById: TimelinesStorage['getTimelineById'] = (id: TimelineIdLiteral) => + getTimelinesInStorageByIds(storage, [id])[id] ?? null; + + const addTimeline: TimelinesStorage['addTimeline'] = (id, timeline) => + addTimelineInStorage(storage, id, timeline); + + return { getAllTimelines, getTimelineById, addTimeline }; +}; + +export { TimelinesStorage }; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/types.ts new file mode 100644 index 0000000000000..d888f3bb8b332 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/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 { TimelineModel } from '../../../timelines/store/timeline/model'; +import { TimelineIdLiteral } from '../../../../common/types/timeline'; + +export interface TimelinesStorage { + getAllTimelines: () => Record; + getTimelineById: (id: TimelineIdLiteral) => TimelineModel | null; + addTimeline: (id: TimelineIdLiteral, timeline: TimelineModel) => void; +} diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index 5a7e5e078c799..2155dc804aa7e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -15,7 +15,7 @@ import { } from 'lodash/fp'; import { Action } from 'redux'; import { Epic } from 'redux-observable'; -import { from, Observable, empty, merge } from 'rxjs'; +import { from, empty, merge } from 'rxjs'; import { filter, map, @@ -34,16 +34,14 @@ import { MatchAllFilter, } from '../../../../../../.../../../src/plugins/data/public'; import { TimelineStatus } from '../../../../common/types/timeline'; +import { inputsModel } from '../../../common/store/inputs'; import { TimelineType, TimelineInput, ResponseTimeline, TimelineResult, } from '../../../graphql/types'; -import { AppApolloClient } from '../../../common/lib/lib'; import { addError } from '../../../common/store/app/actions'; -import { NotesById } from '../../../common/store/app/model'; -import { inputsModel } from '../../../common/store/inputs'; import { applyKqlFilterQuery, @@ -80,18 +78,10 @@ import { epicPersistTimelineFavorite, timelineFavoriteActionsType } from './epic import { isNotNull } from './helpers'; import { dispatcherTimelinePersistQueue } from './epic_dispatcher_timeline_persistence_queue'; import { myEpicTimelineId } from './my_epic_timeline_id'; -import { ActionTimeline, TimelineById } from './types'; +import { ActionTimeline, TimelineEpicDependencies } from './types'; import { persistTimeline } from '../../containers/api'; import { ALL_TIMELINE_QUERY_ID } from '../../containers/all'; -interface TimelineEpicDependencies { - timelineByIdSelector: (state: State) => TimelineById; - timelineTimeRangeSelector: (state: State) => inputsModel.TimeRange; - selectAllTimelineQuery: () => (state: State, id: string) => inputsModel.GlobalQuery; - selectNotesByIdSelector: (state: State) => NotesById; - apolloClient$: Observable; -} - const timelineActionsType = [ applyKqlFilterQuery.type, addProvider.type, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx new file mode 100644 index 0000000000000..34778aba7873c --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + mockGlobalState, + SUB_PLUGINS_REDUCER, + apolloClientObservable, + TestProviders, + defaultHeaders, + createSecuritySolutionStorageMock, + mockIndexPattern, +} from '../../../common/mock'; + +import { createStore, State } from '../../../common/store'; +import { + removeColumn, + upsertColumn, + applyDeltaToColumnWidth, + updateColumns, + updateItemsPerPage, + updateSort, +} from './actions'; + +import { + TimelineComponent, + Props as TimelineComponentProps, +} from '../../components/timeline/timeline'; +import { mockBrowserFields } from '../../../common/containers/source/mock'; +import { mockDataProviders } from '../../components/timeline/data_providers/mock/mock_data_providers'; +import { Sort } from '../../components/timeline/body/sort'; +import { Direction } from '../../../graphql/types'; + +import { addTimelineInStorage } from '../../containers/local_storage'; +import { isPageTimeline } from './epic_local_storage'; + +jest.mock('../../containers/local_storage'); + +const wait = (ms: number = 500): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +const addTimelineInStorageMock = addTimelineInStorage as jest.Mock; + +describe('epicLocalStorage', () => { + const state: State = mockGlobalState; + const { storage } = createSecuritySolutionStorageMock(); + let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); + + let props = {} as TimelineComponentProps; + const sort: Sort = { + columnId: '@timestamp', + sortDirection: Direction.desc, + }; + const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf(); + const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf(); + + const indexPattern = mockIndexPattern; + + beforeEach(() => { + store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage); + props = { + browserFields: mockBrowserFields, + columns: defaultHeaders, + id: 'foo', + dataProviders: mockDataProviders, + end: endDate, + eventType: 'raw' as TimelineComponentProps['eventType'], + filters: [], + indexPattern, + indexToAdd: [], + isLive: false, + itemsPerPage: 5, + itemsPerPageOptions: [5, 10, 20], + kqlMode: 'search' as TimelineComponentProps['kqlMode'], + kqlQueryExpression: '', + loadingIndexName: false, + onChangeItemsPerPage: jest.fn(), + onClose: jest.fn(), + onDataProviderEdited: jest.fn(), + onDataProviderRemoved: jest.fn(), + onToggleDataProviderEnabled: jest.fn(), + onToggleDataProviderExcluded: jest.fn(), + show: true, + showCallOutUnauthorizedMsg: false, + start: startDate, + sort, + toggleColumn: jest.fn(), + usersViewing: ['elastic'], + }; + }); + + it('filters correctly page timelines', () => { + expect(isPageTimeline('timeline-1')).toBe(false); + expect(isPageTimeline('hosts-page-alerts')).toBe(true); + }); + + it('persist adding / reordering of a column correctly', async () => { + shallow( + + + + ); + store.dispatch(upsertColumn({ id: 'test', index: 1, column: defaultHeaders[0] })); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); + + it('persist timeline when removing a column ', async () => { + shallow( + + + + ); + store.dispatch(removeColumn({ id: 'test', columnId: '@timestamp' })); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); + + it('persists resizing of a column', async () => { + shallow( + + + + ); + store.dispatch(applyDeltaToColumnWidth({ id: 'test', columnId: '@timestamp', delta: 80 })); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); + + it('persist the resetting of the fields', async () => { + shallow( + + + + ); + store.dispatch(updateColumns({ id: 'test', columns: defaultHeaders })); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); + + it('persist items per page', async () => { + shallow( + + + + ); + store.dispatch(updateItemsPerPage({ id: 'test', itemsPerPage: 50 })); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); + + it('persist the sorting of a column', async () => { + shallow( + + + + ); + store.dispatch( + updateSort({ + id: 'test', + sort: { + columnId: 'event.severity', + sortDirection: Direction.desc, + }, + }) + ); + await wait(); + expect(addTimelineInStorageMock).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts new file mode 100644 index 0000000000000..b3d1db23ffae8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.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 { Action } from 'redux'; +import { map, filter, ignoreElements, tap, withLatestFrom, delay } from 'rxjs/operators'; +import { Epic } from 'redux-observable'; +import { get } from 'lodash/fp'; + +import { TimelineIdLiteral } from '../../../../common/types/timeline'; +import { addTimelineInStorage } from '../../containers/local_storage'; + +import { + removeColumn, + upsertColumn, + applyDeltaToColumnWidth, + updateColumns, + updateItemsPerPage, + updateSort, +} from './actions'; +import { TimelineEpicDependencies } from './types'; +import { isNotNull } from './helpers'; + +const timelineActionTypes = [ + removeColumn.type, + upsertColumn.type, + applyDeltaToColumnWidth.type, + updateColumns.type, + updateItemsPerPage.type, + updateSort.type, +]; + +export const isPageTimeline = (timelineId: string | undefined): boolean => + // Is not a flyout timeline + !(timelineId && timelineId.toLowerCase().startsWith('timeline')); + +export const createTimelineLocalStorageEpic = (): Epic< + Action, + Action, + State, + TimelineEpicDependencies +> => (action$, state$, { timelineByIdSelector, storage }) => { + const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull)); + return action$.pipe( + delay(500), + withLatestFrom(timeline$), + filter(([action]) => isPageTimeline(get('payload.id', action))), + tap(([action, timelineById]) => { + if (timelineActionTypes.includes(action.type)) { + if (storage) { + const timelineId: TimelineIdLiteral = get('payload.id', action); + addTimelineInStorage(storage, timelineId, timelineById[timelineId]); + } + } + }), + ignoreElements() + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index 03e9ca176ee82..15f956fa79d3c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -23,22 +23,10 @@ import { TimelineTypeLiteral } from '../../../../common/types/timeline'; import { timelineDefaults } from './defaults'; import { ColumnHeaderOptions, KqlMode, TimelineModel, EventType } from './model'; -import { TimelineById, TimelineState } from './types'; - -const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference +import { TimelineById } from './types'; export const isNotNull = (value: T | null): value is T => value !== null; -export const initialTimelineState: TimelineState = { - timelineById: EMPTY_TIMELINE_BY_ID, - autoSavedWarningMsg: { - timelineId: null, - newTimelineModel: null, - }, - showCallOutUnauthorizedMsg: false, - insertTimeline: null, -}; - interface AddTimelineHistoryParams { id: string; historyId: string; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/types.ts index aa6c308614287..5262c72a6140c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/types.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { Action } from 'redux'; +import { Observable } from 'rxjs'; + +import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; +import { AppApolloClient } from '../../../common/lib/lib'; +import { inputsModel } from '../../../common/store/inputs'; +import { NotesById } from '../../../common/store/app/model'; import { TimelineModel } from './model'; export interface AutoSavedWarningMsg { @@ -39,3 +45,12 @@ export interface ActionTimeline extends Action { noteId: string; }; } + +export interface TimelineEpicDependencies { + timelineByIdSelector: (state: State) => TimelineById; + timelineTimeRangeSelector: (state: State) => inputsModel.TimeRange; + selectAllTimelineQuery: () => (state: State, id: string) => inputsModel.GlobalQuery; + selectNotesByIdSelector: (state: State) => NotesById; + apolloClient$: Observable; + storage: Storage; +} diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 6c338d47cf63c..6d59824702cfa 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -12,6 +12,7 @@ import { Start as NewsfeedStart } from '../../../../src/plugins/newsfeed/public' import { Start as InspectorStart } from '../../../../src/plugins/inspector/public'; import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; import { IngestManagerStart } from '../../ingest_manager/public'; import { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, @@ -39,6 +40,7 @@ export interface StartPlugins { export type StartServices = CoreStart & StartPlugins & { security: SecurityPluginSetup; + storage: Storage; }; // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 586b9dec2f4ab..8a77137c20c11 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -27,5 +27,4 @@ export { getPolicyExists } from './lib/detection_engine/index/get_policy_exists' export { createBootstrapIndex } from './lib/detection_engine/index/create_bootstrap_index'; export { getIndexExists } from './lib/detection_engine/index/get_index_exists'; export { buildRouteValidation } from './utils/build_validation/route_validation'; -export { validate } from './lib/detection_engine/routes/rules/validate'; export { transformError, buildSiemResponse } from './lib/detection_engine/routes/utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index fe66496f70dcd..9928ce4807da9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -549,6 +549,7 @@ export const getFindResultStatus = (): SavedObjectsFindResponse< searchAfterTimeDurations: ['200.00'], bulkCreateTimeDurations: ['800.43'], }, + score: 1, references: [], updated_at: '2020-02-18T15:26:51.333Z', version: 'WzQ2LDFd', @@ -570,6 +571,7 @@ export const getFindResultStatus = (): SavedObjectsFindResponse< searchAfterTimeDurations: ['200.00'], bulkCreateTimeDurations: ['800.43'], }, + score: 1, references: [], updated_at: '2020-02-18T15:15:58.860Z', version: 'WzMyLDFd', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 39eea16c6290a..9878521c49322 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { PrePackagedRulesSchema, prePackagedRulesSchema, @@ -18,7 +19,6 @@ import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { validate } from './validate'; export const addPrepackedRulesRoute = (router: IRouter) => { router.put( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 7af58adca7529..92a7ea17e7eaf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { @@ -19,7 +20,7 @@ import { throwHttpError } from '../../../machine_learning/validation'; import { createRules } from '../../rules/create_rules'; import { readRules } from '../../rules/read_rules'; import { getDuplicates } from './utils'; -import { transformValidateBulkError, validate } from './validate'; +import { transformValidateBulkError } from './validate'; import { getIndexExists } from '../../index/get_index_exists'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index 12f908ce7e8b5..99bf16aadc815 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { @@ -14,7 +15,7 @@ import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/ import { IRouter, RouteConfig, RequestHandler } from '../../../../../../../../src/core/server'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { getIdBulkError } from './utils'; -import { transformValidateBulkError, validate } from './validate'; +import { transformValidateBulkError } from './validate'; import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; import { deleteRules } from '../../rules/delete_rules'; import { deleteNotifications } from '../../notifications/delete_notifications'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index c3f4695a20461..bc199ee132e96 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { PrePackagedRulesStatusSchema, prePackagedRulesStatusSchema, @@ -16,7 +17,6 @@ import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { findRules } from '../../rules/find_rules'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { validate } from './validate'; export const getPrepackagedRulesStatusRoute = (router: IRouter) => { router.get( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index c8b61414608a9..a277f97ccf9f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -7,6 +7,7 @@ import { chunk } from 'lodash/fp'; import { extname } from 'path'; +import { validate } from '../../../../../common/validate'; import { importRulesQuerySchema, ImportRulesQuerySchemaDecoded, @@ -39,7 +40,6 @@ import { } from '../utils'; import { patchRules } from '../../rules/patch_rules'; import { getTupleDuplicateErrorsAndUniqueRules } from './utils'; -import { validate } from './validate'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { HapiReadableStream } from '../../rules/types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 16f491547a9e6..b2a9fdd103a68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { patchRulesBulkSchema, @@ -18,7 +19,7 @@ import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwHttpError } from '../../../machine_learning/validation'; import { transformBulkError, buildSiemResponse } from '../utils'; import { getIdBulkError } from './utils'; -import { transformValidateBulkError, validate } from './validate'; +import { transformValidateBulkError } from './validate'; import { patchRules } from '../../rules/patch_rules'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 3ca4a28dd93ee..1e6815a357154 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { validate } from '../../../../../common/validate'; import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -18,7 +19,7 @@ import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwHttpError } from '../../../machine_learning/validation'; import { getIdBulkError } from './utils'; -import { transformValidateBulkError, validate } from './validate'; +import { transformValidateBulkError } from './validate'; import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; import { updateRules } from '../../rules/update_rules'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 07b891e2bf021..1f5442e23d884 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; - import { - validate, transformValidate, transformValidateFindAlerts, transformValidateBulkError, @@ -121,26 +118,6 @@ describe('validate', () => { unSetFeatureFlagsForTestsOnly(); }); - describe('validate', () => { - test('it should do a validation correctly', () => { - const schema = t.exact(t.type({ a: t.number })); - const payload = { a: 1 }; - const [validated, errors] = validate(payload, schema); - - expect(validated).toEqual(payload); - expect(errors).toEqual(null); - }); - - test('it should do an in-validation correctly', () => { - const schema = t.exact(t.type({ a: t.number })); - const payload = { a: 'some other value' }; - const [validated, errors] = validate(payload, schema); - - expect(validated).toEqual(null); - expect(errors).toEqual('Invalid value "some other value" supplied to "a"'); - }); - }); - describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getResult(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index 7b0bf5997d12f..983382b28ab38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -9,6 +9,7 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; +import { validate } from '../../../../../common/validate'; import { findRulesSchema } from '../../../../../common/detection_engine/schemas/response/find_rules_schema'; import { RulesSchema, @@ -113,17 +114,3 @@ export const transformValidateBulkError = ( }); } }; - -export const validate = ( - obj: object, - schema: T -): [t.TypeOf | null, string | null] => { - const decoded = schema.decode(obj); - const checked = exactCheck(obj, decoded); - const left = (errors: t.Errors): [T | null, string | null] => [ - null, - formatErrors(errors).join(','), - ]; - const right = (output: T): [T | null, string | null] => [output, null]; - return pipe(checked, fold(left, right)); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 18c3440ab93e1..ac5132d93a069 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -55,6 +55,7 @@ export const setSignalsStatusRoute = (router: IRouter) => { try { const result = await clusterClient.callAsCurrentUser('updateByQuery', { index: siemClient.getSignalsIndex(), + refresh: 'wait_for', body: { script: { source: `ctx._source.signal.status = '${status}'`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6056e692854af..01ee41e3b877c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -391,7 +391,7 @@ export const exampleFindRuleStatusResponse: ( total: 1, per_page: 6, page: 1, - saved_objects: mockStatuses, + saved_objects: mockStatuses.map((obj) => ({ ...obj, score: 1 })), }); export const mockLogger: Logger = loggingServiceMock.createLogger(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts index b1500e47db843..61cd9cfedd94f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts @@ -462,6 +462,61 @@ describe('get_filter', () => { }, }); }); + + test('it should work with a nested object queries', () => { + const esQuery = getQueryFilter( + 'category:{ name:Frank and trusted:true }', + 'kuery', + [], + ['auditbeat-*'], + [] + ); + expect(esQuery).toEqual({ + bool: { + must: [], + filter: [ + { + nested: { + path: 'category', + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match: { + 'category.name': 'Frank', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + 'category.trusted': true, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + score_mode: 'none', + }, + }, + ], + should: [], + must_not: [], + }, + }); + }); }); describe('getFilter', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts index 0f9e97cfc2106..5080142f22b15 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts @@ -7,6 +7,7 @@ import { extname } from 'path'; import { chunk, omit } from 'lodash/fp'; +import { validate } from '../../../../common/validate'; import { importRulesSchema } from '../../../../common/detection_engine/schemas/response/import_rules_schema'; import { createPromiseFromStreams } from '../../../../../../../src/legacy/utils'; import { IRouter } from '../../../../../../../src/core/server'; @@ -17,7 +18,6 @@ import { SetupPlugins } from '../../../plugin'; import { ConfigType } from '../../../config'; import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; -import { validate } from '../../detection_engine/routes/rules/validate'; import { buildSiemResponse, createBulkErrorObject, diff --git a/x-pack/plugins/spaces/kibana.json b/x-pack/plugins/spaces/kibana.json index 7e6d0425f2ae0..9483cb67392c4 100644 --- a/x-pack/plugins/spaces/kibana.json +++ b/x-pack/plugins/spaces/kibana.json @@ -13,5 +13,6 @@ "savedObjectsManagement" ], "server": true, - "ui": true + "ui": true, + "extraPublicDirs": ["common"] } diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 75cd501a1a9ae..190429d2dacd4 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -138,7 +138,7 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; test(`passes options.type to baseClient if valid singular type specified`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); const expectedReturnValue = { - saved_objects: [createMockResponse()], + saved_objects: [createMockResponse()].map((obj) => ({ ...obj, score: 1 })), total: 1, per_page: 0, page: 0, @@ -158,7 +158,7 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; test(`supplements options with the current namespace`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); const expectedReturnValue = { - saved_objects: [createMockResponse()], + saved_objects: [createMockResponse()].map((obj) => ({ ...obj, score: 1 })), total: 1, per_page: 0, page: 0, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8888120d59f88..7b887867b43b8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8521,28 +8521,18 @@ "xpack.ingestPipelines.form.nameDescription": "このパイプラインの固有の識別子です。", "xpack.ingestPipelines.form.nameFieldLabel": "名前", "xpack.ingestPipelines.form.nameTitle": "名前", - "xpack.ingestPipelines.form.onFailureDescription": "プロセッサーが失敗した後に実行する代替プロセッサー。{learnMoreLink}", - "xpack.ingestPipelines.form.onFailureDocumentionLink": "詳細", - "xpack.ingestPipelines.form.onFailureFieldAriaLabel": "障害プロセッサーJSONエディター", "xpack.ingestPipelines.form.onFailureFieldHelpText": "JSONフォーマットを使用:{code}", "xpack.ingestPipelines.form.onFailureFieldLabel": "障害プロセッサー(任意)", "xpack.ingestPipelines.form.onFailureProcessorsJsonError": "入力が無効です。", - "xpack.ingestPipelines.form.onFailureTitle": "障害プロセッサー", - "xpack.ingestPipelines.form.onFailureToggleDescription": "障害プロセッサーを追加", "xpack.ingestPipelines.form.pipelineNameRequiredError": "名前が必要です。", - "xpack.ingestPipelines.form.processorsDocumentionLink": "詳細", - "xpack.ingestPipelines.form.processorsFieldAriaLabel": "プロセッサーJSONエディター", - "xpack.ingestPipelines.form.processorsFieldDescription": "インデックスの前にドキュメントを変換するために使用するプロセッサー。{learnMoreLink}", "xpack.ingestPipelines.form.processorsFieldHelpText": "JSONフォーマットを使用:{code}", "xpack.ingestPipelines.form.processorsFieldLabel": "プロセッサー", - "xpack.ingestPipelines.form.processorsFieldTitle": "プロセッサー", "xpack.ingestPipelines.form.processorsJsonError": "入力が無効です。", "xpack.ingestPipelines.form.processorsRequiredError": "プロセッサーが必要です。", "xpack.ingestPipelines.form.saveButtonLabel": "パイプラインを保存", "xpack.ingestPipelines.form.savePipelineError": "パイプラインを作成できません", "xpack.ingestPipelines.form.savingButtonLabel": "保存中…", "xpack.ingestPipelines.form.showRequestButtonLabel": "リクエストを表示", - "xpack.ingestPipelines.form.testPipelineButtonLabel": "パイプラインをテスト", "xpack.ingestPipelines.form.versionFieldLabel": "バージョン(任意)", "xpack.ingestPipelines.form.versionToggleDescription": "バージョン番号を追加", "xpack.ingestPipelines.licenseCheckErrorMessage": "ライセンス確認失敗", @@ -10455,7 +10445,6 @@ "xpack.ml.navMenu.dataVisualizerTabLinkText": "データビジュアライザー", "xpack.ml.navMenu.overviewTabLinkText": "概要", "xpack.ml.navMenu.settingsTabLinkText": "設定", - "xpack.ml.newJi18n(ob.recognize.jobsCreationFailed.resetButtonAriaLabel": "リセット", "xpack.ml.newJob.page.createJob": "ジョブを作成", "xpack.ml.newJob.recognize.advancedLabel": "高度な設定", "xpack.ml.newJob.recognize.advancedSettingsAriaLabel": "高度な設定", @@ -16617,7 +16606,6 @@ "xpack.uptime.locationMap.locations.missing.message": "重要な位置情報構成がありません。{codeBlock}フィールドを使用して、アップタイムチェック用に一意の地域を作成できます。", "xpack.uptime.locationMap.locations.missing.message1": "詳細については、ドキュメンテーションを参照してください。", "xpack.uptime.locationMap.locations.missing.title": "地理情報の欠測", - "xpack.uptime.locationMap.locations.tags.others": "{otherLoc}その他 ...", "xpack.uptime.locationName.helpLinkAnnotation": "場所を追加", "xpack.uptime.ml.durationChart.exploreInMlApp": "ML アプリで探索", "xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "異常検知", @@ -16701,12 +16689,7 @@ "xpack.uptime.monitorStatusBar.locations.oneLocStatus": "{loc}場所での{status}", "xpack.uptime.monitorStatusBar.locations.upStatus": "{loc}場所での{status}", "xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel": "監視 URL リンク", - "xpack.uptime.monitorStatusBar.sslCertificate.overview": "証明書概要", "xpack.uptime.monitorStatusBar.sslCertificate.title": "証明書", - "xpack.uptime.monitorStatusBar.sslCertificateExpired.badgeContent": "{emphasizedText}が期限切れになりました", - "xpack.uptime.monitorStatusBar.sslCertificateExpired.label.ariaLabel": "{validityDate}に期限切れになりました", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.badgeContent": "{emphasizedText}が期限切れになります", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.label.ariaLabel": "{validityDate}に期限切れになります", "xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel": "最終確認からの経過時間", "xpack.uptime.navigateToAlertingButton.content": "アラートを管理", "xpack.uptime.navigateToAlertingUi": "Uptime を離れてアラート管理ページに移動します", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3109bab4b9426..e821c4fb22899 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8525,28 +8525,18 @@ "xpack.ingestPipelines.form.nameDescription": "此管道的唯一标识符。", "xpack.ingestPipelines.form.nameFieldLabel": "名称", "xpack.ingestPipelines.form.nameTitle": "名称", - "xpack.ingestPipelines.form.onFailureDescription": "处理器失败后要执行的备用处理器。{learnMoreLink}", - "xpack.ingestPipelines.form.onFailureDocumentionLink": "了解详情", - "xpack.ingestPipelines.form.onFailureFieldAriaLabel": "失败处理器 JSON 编辑器", "xpack.ingestPipelines.form.onFailureFieldHelpText": "使用 JSON 格式:{code}", "xpack.ingestPipelines.form.onFailureFieldLabel": "失败处理器(可选)", "xpack.ingestPipelines.form.onFailureProcessorsJsonError": "输入无效。", - "xpack.ingestPipelines.form.onFailureTitle": "失败处理器", - "xpack.ingestPipelines.form.onFailureToggleDescription": "添加失败处理器", "xpack.ingestPipelines.form.pipelineNameRequiredError": "“名称”必填。", - "xpack.ingestPipelines.form.processorsDocumentionLink": "了解详情", - "xpack.ingestPipelines.form.processorsFieldAriaLabel": "处理器 JSON 编辑器", - "xpack.ingestPipelines.form.processorsFieldDescription": "用于在索引之前转换文档的处理器。{learnMoreLink}", "xpack.ingestPipelines.form.processorsFieldHelpText": "使用 JSON 格式:{code}", "xpack.ingestPipelines.form.processorsFieldLabel": "处理器", - "xpack.ingestPipelines.form.processorsFieldTitle": "处理器", "xpack.ingestPipelines.form.processorsJsonError": "输入无效。", "xpack.ingestPipelines.form.processorsRequiredError": "需要指定处理器。", "xpack.ingestPipelines.form.saveButtonLabel": "保存管道", "xpack.ingestPipelines.form.savePipelineError": "无法创建管道", "xpack.ingestPipelines.form.savingButtonLabel": "正在保存......", "xpack.ingestPipelines.form.showRequestButtonLabel": "显示请求", - "xpack.ingestPipelines.form.testPipelineButtonLabel": "测试管道", "xpack.ingestPipelines.form.versionFieldLabel": "版本(可选)", "xpack.ingestPipelines.form.versionToggleDescription": "添加版本号", "xpack.ingestPipelines.licenseCheckErrorMessage": "许可证检查失败", @@ -10459,7 +10449,6 @@ "xpack.ml.navMenu.dataVisualizerTabLinkText": "数据可视化工具", "xpack.ml.navMenu.overviewTabLinkText": "概览", "xpack.ml.navMenu.settingsTabLinkText": "设置", - "xpack.ml.newJi18n(ob.recognize.jobsCreationFailed.resetButtonAriaLabel": "重置", "xpack.ml.newJob.page.createJob": "创建作业", "xpack.ml.newJob.recognize.advancedLabel": "高级", "xpack.ml.newJob.recognize.advancedSettingsAriaLabel": "高级设置", @@ -16623,7 +16612,6 @@ "xpack.uptime.locationMap.locations.missing.message": "重要的地理位置配置缺失。您可以使用 {codeBlock} 字段为您的运行时间检查创建独特的地理区域。", "xpack.uptime.locationMap.locations.missing.message1": "在我们的文档中获取更多的信息。", "xpack.uptime.locationMap.locations.missing.title": "地理信息缺失", - "xpack.uptime.locationMap.locations.tags.others": "{otherLoc} 其他......", "xpack.uptime.locationName.helpLinkAnnotation": "添加位置", "xpack.uptime.ml.durationChart.exploreInMlApp": "在 ML 应用中浏览", "xpack.uptime.ml.enableAnomalyDetectionPanel.anomalyDetectionTitle": "异常检测", @@ -16707,12 +16695,7 @@ "xpack.uptime.monitorStatusBar.locations.oneLocStatus": "在 {loc} 位置{status}", "xpack.uptime.monitorStatusBar.locations.upStatus": "在 {loc} 位置{status}", "xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel": "监测 URL 链接", - "xpack.uptime.monitorStatusBar.sslCertificate.overview": "证书概览", "xpack.uptime.monitorStatusBar.sslCertificate.title": "证书", - "xpack.uptime.monitorStatusBar.sslCertificateExpired.badgeContent": "{emphasizedText}过期", - "xpack.uptime.monitorStatusBar.sslCertificateExpired.label.ariaLabel": "已于 {validityDate}过期", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.badgeContent": "{emphasizedText}过期", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.label.ariaLabel": "将于 {validityDate}过期", "xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel": "自上次检查以来经过的时间", "xpack.uptime.navigateToAlertingButton.content": "管理告警", "xpack.uptime.navigateToAlertingUi": "离开 Uptime 并前往“Alerting 管理”页面", diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index d8f5055368831..158cfa100d546 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -5,5 +5,6 @@ "ui": true, "optionalPlugins": ["alerts", "alertingBuiltins"], "requiredPlugins": ["management", "charts", "data"], - "configPath": ["xpack", "trigger_actions_ui"] + "configPath": ["xpack", "trigger_actions_ui"], + "extraPublicDirs": ["public/common", "public/common/constants"] } diff --git a/x-pack/plugins/ui_actions_enhanced/README.md b/x-pack/plugins/ui_actions_enhanced/README.md new file mode 100644 index 0000000000000..1a72a431e3975 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/README.md @@ -0,0 +1,3 @@ +# `ui_actions_enhanced` + +- [__Dashboard drilldown user docs__](https://www.elastic.co/guide/en/kibana/master/drilldowns.html) diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx index 4da4d648bc0ec..5d9804d2a5c33 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.tsx @@ -103,7 +103,10 @@ export class CustomTimeRangeAction implements ActionByType + />, + { + 'data-test-subj': 'customizeTimeRangeModal', + } ); } } diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx index 59a2fc27267b0..28d99db9b3ef7 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.tsx @@ -82,7 +82,10 @@ export class CustomTimeRangeBadge implements ActionByType + />, + { + 'data-test-subj': 'customizeTimeRangeModal', + } ); } } diff --git a/x-pack/plugins/ui_actions_enhanced/public/customize_time_range_modal.tsx b/x-pack/plugins/ui_actions_enhanced/public/customize_time_range_modal.tsx index 8a9eeacb66564..993720775a29f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/customize_time_range_modal.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/customize_time_range_modal.tsx @@ -101,7 +101,7 @@ export class CustomizeTimeRangeModal extends Component - + - +

diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts index 3e37bac0a17af..58344026079e7 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts @@ -12,7 +12,10 @@ import { UiActionsActionDefinition as ActionDefinition, } from '../../../../../src/plugins/ui_actions/public'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; -import { StateContainer, createStateContainer } from '../../../../../src/plugins/kibana_utils'; +import { + StateContainer, + createStateContainer, +} from '../../../../../src/plugins/kibana_utils/common'; import { StartContract } from '../plugin'; import { SerializedAction, SerializedEvent } from './types'; diff --git a/x-pack/plugins/uptime/common/runtime_types/common.ts b/x-pack/plugins/uptime/common/runtime_types/common.ts index e07c46fa01cfe..603d242d4dcd0 100644 --- a/x-pack/plugins/uptime/common/runtime_types/common.ts +++ b/x-pack/plugins/uptime/common/runtime_types/common.ts @@ -6,15 +6,19 @@ import * as t from 'io-ts'; -export const LocationType = t.partial({ +export const LocationType = t.type({ lat: t.string, lon: t.string, }); -export const CheckGeoType = t.partial({ - name: t.string, - location: LocationType, -}); +export const CheckGeoType = t.intersection([ + t.type({ + name: t.string, + }), + t.partial({ + location: LocationType, + }), +]); export const SummaryType = t.partial({ up: t.number, @@ -34,5 +38,6 @@ export const DateRangeType = t.type({ export type Summary = t.TypeOf; export type Location = t.TypeOf; +export type GeoPoint = t.TypeOf; export type StatesIndexStatus = t.TypeOf; export type DateRange = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor/locations.ts b/x-pack/plugins/uptime/common/runtime_types/monitor/locations.ts index ea3cfe677ca99..00ed1dc407e98 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/locations.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor/locations.ts @@ -7,17 +7,23 @@ import * as t from 'io-ts'; import { CheckGeoType, SummaryType } from '../common'; // IO type for validation -export const MonitorLocationType = t.partial({ +export const MonitorLocationType = t.type({ + up_history: t.number, + down_history: t.number, + timestamp: t.string, summary: SummaryType, geo: CheckGeoType, - timestamp: t.string, }); // Typescript type for type checking export type MonitorLocation = t.TypeOf; export const MonitorLocationsType = t.intersection([ - t.type({ monitorId: t.string }), + t.type({ + monitorId: t.string, + up_history: t.number, + down_history: t.number, + }), t.partial({ locations: t.array(MonitorLocationType) }), ]); export type MonitorLocations = t.TypeOf; diff --git a/x-pack/plugins/uptime/public/components/certificates/translations.ts b/x-pack/plugins/uptime/public/components/certificates/translations.ts index 518eddf1211a4..176625d647ca0 100644 --- a/x-pack/plugins/uptime/public/components/certificates/translations.ts +++ b/x-pack/plugins/uptime/public/components/certificates/translations.ts @@ -18,6 +18,10 @@ export const EXPIRES_SOON = i18n.translate('xpack.uptime.certs.expireSoon', { defaultMessage: 'Expires soon', }); +export const EXPIRES = i18n.translate('xpack.uptime.certs.expires', { + defaultMessage: 'Expires', +}); + export const SEARCH_CERTS = i18n.translate('xpack.uptime.certs.searchCerts', { defaultMessage: 'Search certificates', }); diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap index b389139d71dbf..1db4a87b1c37b 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap @@ -7,6 +7,10 @@ exports[`MonitorBarSeries component renders if the data series is present 1`] =
+
-
-

- No data to display -

-
-
+ />
`; diff --git a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap index db07faae44272..668e4e0c064a8 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__tests__/__snapshots__/ping_histogram.test.tsx.snap @@ -14,6 +14,10 @@ Array [
+
-
-

- No data to display -

-
-
+ />
, ] diff --git a/x-pack/plugins/uptime/public/components/common/translations.ts b/x-pack/plugins/uptime/public/components/common/translations.ts new file mode 100644 index 0000000000000..d2c466ddf0c83 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/common/translations.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 { i18n } from '@kbn/i18n'; + +export const URL_LABEL = i18n.translate('xpack.uptime.monitorList.table.url.name', { + defaultMessage: 'Url', +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/index.ts b/x-pack/plugins/uptime/public/components/monitor/index.ts index cb7b27afded02..fd9a9a2c897d7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/index.ts +++ b/x-pack/plugins/uptime/public/components/monitor/index.ts @@ -6,7 +6,7 @@ export * from './ml'; export * from './ping_list'; -export * from './location_map'; -export * from './monitor_status_details'; +export * from './status_details/location_map'; +export * from './status_details'; export * from './ping_histogram'; export * from './monitor_charts'; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_map.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_map.test.tsx.snap deleted file mode 100644 index 7b847782fe1ac..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_map.test.tsx.snap +++ /dev/null @@ -1,282 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LocationMap component doesnt shows warning if geo is provided 1`] = ` - - - - - - - - - - - - - - -`; - -exports[`LocationMap component renders correctly against snapshot 1`] = ` - - - - - - - - - - - - - - - -`; - -exports[`LocationMap component renders named locations that have missing geo data 1`] = ` - - - - - - - - - - - - - - - -`; - -exports[`LocationMap component shows warning if geo information is missing 1`] = ` - - - - - - - - - - - - - - - -`; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap deleted file mode 100644 index f2a390792918a..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap +++ /dev/null @@ -1,682 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LocationStatusTags component renders properly against props 1`] = ` - - - - - - - - Berlin - - - - - - 1 Mon ago - - - - - - - - - - Berlin - - - - - - 1 Mon ago - - - - - - - - Islamabad - - - - - - 1 Mon ago - - - - - - -`; - -exports[`LocationStatusTags component renders when all locations are down 1`] = ` -.c3 { - display: inline-block; - margin-left: 4px; -} - -.c2 { - font-weight: 600; -} - -.c1 { - margin-bottom: 5px; - white-space: nowrap; -} - -.c0 { - max-height: 229px; - overflow: hidden; - margin-top: auto; -} - -@media (max-width:1042px) { - .c1 { - display: inline-block; - margin-right: 16px; - } -} - -
- -
- - - -
-
- Islamabad -
-
-
-
-
- -
-
- 5s ago -
-
-
-
-
- - - -
-
- Berlin -
-
-
-
-
- -
-
- 5m ago -
-
-
-
-
- -
-`; - -exports[`LocationStatusTags component renders when all locations are up 1`] = ` -.c3 { - display: inline-block; - margin-left: 4px; -} - -.c2 { - font-weight: 600; -} - -.c1 { - margin-bottom: 5px; - white-space: nowrap; -} - -.c0 { - max-height: 229px; - overflow: hidden; - margin-top: auto; -} - -@media (max-width:1042px) { - .c1 { - display: inline-block; - margin-right: 16px; - } -} - -
- - -
- - - -
-
- Berlin -
-
-
-
-
- -
-
- 5d ago -
-
-
-
-
- - - -
-
- Islamabad -
-
-
-
-
- -
-
- 5s ago -
-
-
-
-
-
-`; - -exports[`LocationStatusTags component renders when there are many location 1`] = ` -Array [ - .c3 { - display: inline-block; - margin-left: 4px; -} - -.c2 { - font-weight: 600; -} - -.c1 { - margin-bottom: 5px; - white-space: nowrap; -} - -.c0 { - max-height: 229px; - overflow: hidden; - margin-top: auto; -} - -@media (max-width:1042px) { - .c1 { - display: inline-block; - margin-right: 16px; - } -} - -
- -
- - - -
-
- Islamabad -
-
-
-
-
- -
-
- 5s ago -
-
-
-
-
- - - -
-
- Berlin -
-
-
-
-
- -
-
- 5m ago -
-
-
-
-
- - - -
-
- st-paul -
-
-
-
-
- -
-
- 5h ago -
-
-
-
-
- - - -
-
- Tokyo -
-
-
-
-
- -
-
- 5d ago -
-
-
-
-
- - - -
-
- New York -
-
-
-
-
- -
-
- 1 Mon ago -
-
-
-
-
- - - -
-
- Toronto -
-
-
-
-
- -
-
- 5 Mon ago -
-
-
-
-
- - - -
-
- Sydney -
-
-
-
-
- -
-
- 5 Yr ago -
-
-
-
-
- - - -
-
- Paris -
-
-
-
-
- -
-
- 5 Yr ago -
-
-
-
-
- -
, - .c0 { - padding-left: 18px; -} - -@media (max-width:1042px) { - -} - -
-
-
-

- 1 Others ... -

-
-
-
, -] -`; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/location_map.tsx b/x-pack/plugins/uptime/public/components/monitor/location_map/location_map.tsx deleted file mode 100644 index 916f1cbb63e53..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/location_map.tsx +++ /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 React from 'react'; -import styled from 'styled-components'; -import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary, EuiHideFor } from '@elastic/eui'; -import { LocationStatusTags } from './location_status_tags'; -import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; -import { MonitorLocations, MonitorLocation } from '../../../../common/runtime_types'; -import { UNNAMED_LOCATION } from '../../../../common/constants'; -import { LocationMissingWarning } from './location_missing'; - -// These height/width values are used to make sure map is in center of panel -// And to make sure, it doesn't take too much space -const MapPanel = styled.div` - height: 240px; - width: 520px; - @media (min-width: 1300px) { - margin-right: 20px; - } - @media (max-width: 574px) { - height: 250px; - width: 100%; - margin-right: 0; - } -`; - -const EuiFlexItemTags = styled(EuiFlexItem)` - padding-top: 5px; - @media (max-width: 1042px) { - flex-basis: 80% !important; - flex-grow: 0 !important; - order: 1; - } -`; - -const FlexGroup = styled(EuiFlexGroup)` - @media (max-width: 850px) { - justify-content: center; - } -`; - -interface LocationMapProps { - monitorLocations: MonitorLocations; -} - -export const LocationMap = ({ monitorLocations }: LocationMapProps) => { - const upPoints: LocationPoint[] = []; - const downPoints: LocationPoint[] = []; - - let isGeoInfoMissing = false; - - if (monitorLocations?.locations) { - monitorLocations.locations.forEach((item: MonitorLocation) => { - if (item.geo?.name === UNNAMED_LOCATION || !item.geo?.location) { - isGeoInfoMissing = true; - } else if ( - item.geo?.name !== UNNAMED_LOCATION && - !!item.geo.location.lat && - !!item.geo.location.lon - ) { - // TypeScript doesn't infer that the above checks in this block's condition - // ensure that lat and lon are defined when we try to pass the location object directly, - // but if we destructure the values it does. Improvement to this block is welcome. - const { lat, lon } = item.geo.location; - if (item?.summary?.down === 0) { - upPoints.push({ lat, lon }); - } else { - downPoints.push({ lat, lon }); - } - } - }); - } - - return ( - - - - - - - - {isGeoInfoMissing && } - - - - - - - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/location_status_tags.tsx b/x-pack/plugins/uptime/public/components/monitor/location_map/location_status_tags.tsx deleted file mode 100644 index db84fd5e2ca42..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/location_status_tags.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useContext } from 'react'; -import moment from 'moment'; -import styled from 'styled-components'; -import { EuiBadge, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { UptimeThemeContext } from '../../../contexts'; -import { MonitorLocation } from '../../../../common/runtime_types'; -import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../common/constants'; - -const TimeStampSpan = styled.span` - display: inline-block; - margin-left: 4px; -`; - -const TextStyle = styled.div` - font-weight: 600; -`; - -const BadgeItem = styled.div` - margin-bottom: 5px; - white-space: nowrap; - @media (max-width: 1042px) { - display: inline-block; - margin-right: 16px; - } -`; - -// Set height so that it remains within panel, enough height to display 7 locations tags -const TagContainer = styled.div` - max-height: 229px; - overflow: hidden; - margin-top: auto; -`; - -const OtherLocationsDiv = styled.div` - padding-left: 18px; -`; - -interface Props { - locations: MonitorLocation[]; -} - -interface StatusTag { - label: string; - timestamp: number; -} - -export const LocationStatusTags = ({ locations }: Props) => { - const { - colors: { gray, danger }, - } = useContext(UptimeThemeContext); - - const upLocations: StatusTag[] = []; - const downLocations: StatusTag[] = []; - - locations.forEach((item: any) => { - if (item.summary.down === 0) { - upLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() }); - } else { - downLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() }); - } - }); - - // Sort lexicographically by label - upLocations.sort((a, b) => { - return a.label > b.label ? 1 : b.label > a.label ? -1 : 0; - }); - - const tagLabel = (item: StatusTag, ind: number, color: string) => { - return ( - - - - {item.label} - - - - {moment(item.timestamp).fromNow()} - - - ); - }; - - const prevLocal: string = moment.locale() ?? 'en'; - - const renderTags = () => { - const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE; - if (!shortLocale) { - moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE); - } - - const tags = ( - - {downLocations.map((item, ind) => tagLabel(item, ind, danger))} - {upLocations.map((item, ind) => tagLabel(item, ind, gray))} - - ); - - // Need to reset locale so it doesn't effect other parts of the app - moment.locale(prevLocal); - return tags; - }; - - return ( - <> - {renderTags()} - {locations.length > 7 && ( - - -

- -

-
-
- )} - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap deleted file mode 100644 index ff63b3695fb8d..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MonitorStatusBar component renders duration in ms, not us 1`] = ` -
-
-
-

- Up in 2 Locations -

-
-
- -
- -

- id1 -

-
-
-
-
-
-`; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx deleted file mode 100644 index 73b58e8a33f6b..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import moment from 'moment'; -import { i18n } from '@kbn/i18n'; -import { Link } from 'react-router-dom'; -import { EuiSpacer, EuiText, EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { Tls } from '../../../../../common/runtime_types'; -import { useCertStatus } from '../../../../hooks'; -import { CERT_STATUS, CERTIFICATES_ROUTE } from '../../../../../common/constants'; - -interface Props { - /** - * TLS information coming from monitor in ES heartbeat index - */ - tls: Tls | null | undefined; -} - -export const MonitorSSLCertificate = ({ tls }: Props) => { - const certStatus = useCertStatus(tls?.not_after); - - const isExpiringSoon = certStatus === CERT_STATUS.EXPIRING_SOON; - - const isExpired = certStatus === CERT_STATUS.EXPIRED; - - const relativeDate = moment(tls?.not_after).fromNow(); - - return certStatus ? ( - <> - - {i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.title', { - defaultMessage: 'Certificate:', - })} - - - - - - {isExpired ? ( - {relativeDate}, - }} - /> - ) : ( - - {relativeDate} - - ), - }} - /> - )} - - - - - - {i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.overview', { - defaultMessage: 'Certificate overview', - })} - - - - - - ) : null; -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar.tsx deleted file mode 100644 index 36159dc29eccd..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar.tsx +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { - EuiLink, - EuiTitle, - EuiTextColor, - EuiSpacer, - EuiText, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; -import { MonitorSSLCertificate } from './ssl_certificate'; -import * as labels from './translations'; -import { StatusByLocations } from './status_by_location'; -import { Ping } from '../../../../../common/runtime_types'; -import { MonitorLocations } from '../../../../../common/runtime_types'; - -interface MonitorStatusBarProps { - monitorId: string; - monitorStatus: Ping | null; - monitorLocations: MonitorLocations; -} - -export const MonitorStatusBarComponent: React.FC = ({ - monitorId, - monitorStatus, - monitorLocations, -}) => { - const full = monitorStatus?.url?.full ?? ''; - - return ( - - - - - - - - {full} - - - - - - -

{monitorId}

-
-
-
- - - - -
- ); -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/translations.ts b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/translations.ts deleted file mode 100644 index f60a1ceeaafb8..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/translations.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 { i18n } from '@kbn/i18n'; - -export const healthStatusMessageAriaLabel = i18n.translate( - 'xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel', - { - defaultMessage: 'Monitor status', - } -); - -export const upLabel = i18n.translate('xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel', { - defaultMessage: 'Up', -}); - -export const downLabel = i18n.translate( - 'xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel', - { - defaultMessage: 'Down', - } -); - -export const monitorUrlLinkAriaLabel = i18n.translate( - 'xpack.uptime.monitorStatusBar.monitorUrlLinkAriaLabel', - { - defaultMessage: 'Monitor URL link', - } -); - -export const durationTextAriaLabel = i18n.translate( - 'xpack.uptime.monitorStatusBar.durationTextAriaLabel', - { - defaultMessage: 'Monitor duration in milliseconds', - } -); - -export const timestampFromNowTextAriaLabel = i18n.translate( - 'xpack.uptime.monitorStatusBar.timestampFromNowTextAriaLabel', - { - defaultMessage: 'Time since last check', - } -); - -export const loadingMessage = i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', { - defaultMessage: 'Loading…', -}); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap new file mode 100644 index 0000000000000..d53f338b60aed --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MonitorStatusBar component renders 1`] = ` +Array [ +
+
+

+ Up in 2 Locations +

+
+
, +
, + .c0.c0.c0 { + width: 35%; +} + +.c1.c1.c1 { + width: 65%; + overflow-wrap: anywhere; +} + +
+
+ Overall availability +
+
+ 0.00 % +
+
+ Url +
+
+ + +
+ +
+
+ Monitor ID +
+
+
, +] +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap similarity index 61% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap rename to x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap index 628e1d576181e..5b63a09d4f7c4 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap @@ -2,61 +2,91 @@ exports[`SSL Certificate component renders 1`] = ` Array [ -
- Certificate: -
, + TLS Certificate + ,
, -
-
-
- Expires - - - - in 2 months - - - -
-
- , + + + , ] `; -exports[`SSL Certificate component renders null if invalid date 1`] = `null`; +exports[`SSL Certificate component renders null if invalid date 1`] = ` +Array [ + .c0.c0.c0 { + width: 35%; +} + +
+ TLS Certificate +
, +
, + .c0.c0.c0 { + width: 65%; + overflow-wrap: anywhere; +} + +
+ + + -- + + +
, +] +`; exports[`SSL Certificate component shallow renders 1`] = ` { let monitorStatus: Ping; @@ -49,18 +50,21 @@ describe('MonitorStatusBar component', () => { const spy = jest.spyOn(redux, 'useDispatch'); spy.mockReturnValue(jest.fn()); - const spy1 = jest.spyOn(redux, 'useSelector'); - spy1.mockReturnValue(true); + jest.spyOn(redux, 'useSelector').mockImplementation((fn, d) => { + if (fn.name === ' monitorStatusSelector') { + return monitorStatus; + } else { + return monitorLocations; + } + }); }); - it('renders duration in ms, not us', () => { - const component = renderWithIntl( - - ); + it('renders', () => { + const history = createMemoryHistory({ + initialEntries: ['/aWQx/'], + }); + history.location.key = 'test'; + const component = renderWithRouter(, history); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/ssl_certificate.test.tsx similarity index 75% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/__test__/ssl_certificate.test.tsx index e8ffc3bf26c42..a4b360ea690d0 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/ssl_certificate.test.tsx @@ -6,9 +6,9 @@ import React from 'react'; import moment from 'moment'; -import { EuiBadge } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import { Tls } from '../../../../../common/runtime_types'; -import { MonitorSSLCertificate } from '../monitor_status_bar'; +import { MonitorSSLCertificate } from '../status_bar'; import * as redux from 'react-redux'; import { mountWithRouter, renderWithRouter, shallowWithRouter } from '../../../../lib'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../common/constants'; @@ -58,14 +58,12 @@ describe('SSL Certificate component', () => { }; const component = mountWithRouter(); - const badgeComponent = component.find(EuiBadge); + const lockIcon = component.find(EuiIcon); - expect(badgeComponent.props().color).toBe('warning'); + expect(lockIcon.props().color).toBe('warning'); - const badgeComponentText = component.find('.euiBadge__text'); - expect(badgeComponentText.text()).toBe(moment(dateIn5Days).fromNow()); - - expect(badgeComponent.find('span.euiBadge--warning')).toBeTruthy(); + const componentText = component.find('h4'); + expect(componentText.text()).toBe('Expires soon ' + moment(dateIn5Days).fromNow()); }); it('does not render the expiration date with a warning state if expiry date is greater than a month', () => { @@ -75,12 +73,10 @@ describe('SSL Certificate component', () => { }; const component = mountWithRouter(); - const badgeComponent = component.find(EuiBadge); - expect(badgeComponent.props().color).toBe('default'); - - const badgeComponentText = component.find('.euiBadge__text'); - expect(badgeComponentText.text()).toBe(moment(dateIn40Days).fromNow()); + const lockIcon = component.find(EuiIcon); + expect(lockIcon.props().color).toBe('success'); - expect(badgeComponent.find('span.euiBadge--warning')).toHaveLength(0); + const componentText = component.find('h4'); + expect(componentText.text()).toBe('Expires ' + moment(dateIn40Days).fromNow()); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/status_by_location.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/status_by_location.test.tsx similarity index 77% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/status_by_location.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/__test__/status_by_location.test.tsx index b2619825311d7..b171a8bedb8a7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/status_by_location.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/__test__/status_by_location.test.tsx @@ -17,10 +17,16 @@ describe('StatusByLocation component', () => { { summary: { up: 4, down: 0 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, { summary: { up: 4, down: 0 }, geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = shallowWithIntl(); @@ -32,10 +38,16 @@ describe('StatusByLocation component', () => { { summary: { up: 4, down: 0 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, { summary: { up: 4, down: 0 }, geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = renderWithIntl(); @@ -47,6 +59,9 @@ describe('StatusByLocation component', () => { { summary: { up: 4, down: 0 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = renderWithIntl(); @@ -58,6 +73,9 @@ describe('StatusByLocation component', () => { { summary: { up: 0, down: 4 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = renderWithIntl(); @@ -69,10 +87,16 @@ describe('StatusByLocation component', () => { { summary: { up: 0, down: 4 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, { summary: { up: 0, down: 4 }, geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = renderWithIntl(); @@ -84,10 +108,16 @@ describe('StatusByLocation component', () => { { summary: { up: 0, down: 4 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, { summary: { up: 4, down: 0 }, geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + up_history: 4, + down_history: 0, + timestamp: '2020-01-13T22:50:06.536Z', }, ]; const component = renderWithIntl(); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap new file mode 100644 index 0000000000000..9496274a69171 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/availability_reporting.test.tsx.snap @@ -0,0 +1,381 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AvailabilityReporting component renders correctly against snapshot 1`] = ` +Array [ + @media (max-width:1042px) { + +} + +
, + .c0 { + white-space: nowrap; + display: inline-block; +} + +@media (max-width:1042px) { + .c0 { + display: inline-block; + margin-right: 16px; + } +} + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Location + +
+
+
+ + Availability + +
+
+
+ + Last check + +
+
+
+ Location +
+
+
+ + + +
+

+ au-heartbeat +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 36m ago + +
+
+
+ Location +
+
+
+ + + +
+

+ nyc-heartbeat +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 36m ago + +
+
+
+ Location +
+
+
+ + + +
+

+ spa-heartbeat +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 36m ago + +
+
+
+
, +] +`; + +exports[`AvailabilityReporting component shallow renders correctly against snapshot 1`] = ` + + + + +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap new file mode 100644 index 0000000000000..05e0b50a86f35 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/location_status_tags.test.tsx.snap @@ -0,0 +1,1085 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationStatusTags component renders properly against props 1`] = ` + + + + + +`; + +exports[`LocationStatusTags component renders when all locations are down 1`] = ` +.c1 { + white-space: nowrap; + display: inline-block; +} + +.c0 { + max-height: 246px; + overflow: hidden; +} + +@media (max-width:1042px) { + .c1 { + display: inline-block; + margin-right: 16px; + } +} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + Location + +
+
+
+ + Availability + +
+
+
+ + Last check + +
+
+
+ Location +
+
+
+ + + +
+

+ Berlin +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5m ago + +
+
+
+ Location +
+
+
+ + + +
+

+ Islamabad +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5s ago + +
+
+
+
+
+`; + +exports[`LocationStatusTags component renders when all locations are up 1`] = ` +.c1 { + white-space: nowrap; + display: inline-block; +} + +.c0 { + max-height: 246px; + overflow: hidden; +} + +@media (max-width:1042px) { + .c1 { + display: inline-block; + margin-right: 16px; + } +} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + Location + +
+
+
+ + Availability + +
+
+
+ + Last check + +
+
+
+ Location +
+
+
+ + + +
+

+ Berlin +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5d ago + +
+
+
+ Location +
+
+
+ + + +
+

+ Islamabad +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5s ago + +
+
+
+
+
+`; + +exports[`LocationStatusTags component renders when there are many location 1`] = ` +.c1 { + white-space: nowrap; + display: inline-block; +} + +.c0 { + max-height: 246px; + overflow: hidden; +} + +@media (max-width:1042px) { + .c1 { + display: inline-block; + margin-right: 16px; + } +} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Location + +
+
+
+ + Availability + +
+
+
+ + Last check + +
+
+
+ Location +
+
+
+ + + +
+

+ Berlin +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5m ago + +
+
+
+ Location +
+
+
+ + + +
+

+ Islamabad +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5s ago + +
+
+
+ Location +
+
+
+ + + +
+

+ New York +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 1 Mon ago + +
+
+
+ Location +
+
+
+ + + +
+

+ Paris +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5 Yr ago + +
+
+
+ Location +
+
+
+ + + +
+

+ Sydney +

+
+
+
+
+
+
+
+
+ Availability +
+
+ + 100.00 % + +
+
+
+ Last check +
+
+ + 5 Yr ago + +
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+
+`; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap new file mode 100644 index 0000000000000..3381efa62286b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/__snapshots__/tag_label.test.tsx.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TagLabel component renders correctly against snapshot 1`] = ` +.c0 { + white-space: nowrap; + display: inline-block; +} + +@media (max-width:1042px) { + .c0 { + display: inline-block; + margin-right: 16px; + } +} + +
+ + + +
+

+ US-East +

+
+
+
+
+
+`; + +exports[`TagLabel component shallow render correctly against snapshot 1`] = ` + + + +

+ US-East +

+
+
+
+`; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx new file mode 100644 index 0000000000000..de9f6b0d3b30f --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/availability_reporting.test.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { AvailabilityReporting } from '../availability_reporting'; +import { StatusTag } from '../location_status_tags'; + +describe('AvailabilityReporting component', () => { + let allLocations: StatusTag[]; + + beforeEach(() => { + allLocations = [ + { + label: 'au-heartbeat', + timestamp: '36m ago', + color: '#d3dae6', + availability: 100, + }, + { + label: 'nyc-heartbeat', + timestamp: '36m ago', + color: '#d3dae6', + availability: 100, + }, + { label: 'spa-heartbeat', timestamp: '36m ago', color: '#d3dae6', availability: 100 }, + ]; + }); + + it('shallow renders correctly against snapshot', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders correctly against snapshot', () => { + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_status_tags.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx similarity index 84% rename from x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_status_tags.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx index 28b4482401793..bfeaa6085e998 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_status_tags.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/location_status_tags.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import moment from 'moment'; import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { MonitorLocation } from '../../../../../common/runtime_types/monitor'; +import { MonitorLocation } from '../../../../../../common/runtime_types/monitor'; import { LocationStatusTags } from '../index'; describe('LocationStatusTags component', () => { @@ -19,16 +19,22 @@ describe('LocationStatusTags component', () => { summary: { up: 4, down: 0 }, geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'w').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'w').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 2 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'w').toISOString(), + up_history: 4, + down_history: 0, }, ]; const component = shallowWithIntl(); @@ -41,41 +47,57 @@ describe('LocationStatusTags component', () => { summary: { up: 0, down: 1 }, geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 's').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'm').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'h').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'd').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'New York', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'w').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'Toronto', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'M').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'Sydney', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'y').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 1 }, geo: { name: 'Paris', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'y').toISOString(), + up_history: 4, + down_history: 0, }, ]; const component = renderWithIntl(); @@ -88,11 +110,15 @@ describe('LocationStatusTags component', () => { summary: { up: 4, down: 0 }, geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 's').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'd').toISOString(), + up_history: 4, + down_history: 0, }, ]; const component = renderWithIntl(); @@ -105,11 +131,15 @@ describe('LocationStatusTags component', () => { summary: { up: 0, down: 2 }, geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 's').toISOString(), + up_history: 4, + down_history: 0, }, { summary: { up: 0, down: 2 }, geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: moment().subtract('5', 'm').toISOString(), + up_history: 4, + down_history: 0, }, ]; const component = renderWithIntl(); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx new file mode 100644 index 0000000000000..3560784122298 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__tests__/tag_label.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { TagLabel } from '../tag_label'; + +describe('TagLabel component', () => { + it('shallow render correctly against snapshot', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders correctly against snapshot', () => { + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx new file mode 100644 index 0000000000000..8fed5db5e0271 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/availability_reporting.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiBasicTable, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Pagination } from '@elastic/eui/src/components/basic_table/pagination_bar'; +import { StatusTag } from './location_status_tags'; +import { TagLabel } from './tag_label'; +import { AvailabilityLabel, LastCheckLabel, LocationLabel } from '../translations'; + +interface Props { + allLocations: StatusTag[]; +} + +export const formatAvailabilityValue = (val: number) => { + const result = Math.round(val * 100) / 100; + return result.toFixed(2); +}; + +export const AvailabilityReporting: React.FC = ({ allLocations }) => { + const [pageIndex, setPageIndex] = useState(0); + + const cols = [ + { + field: 'label', + name: LocationLabel, + truncateText: true, + render: (val: string, item: StatusTag) => { + return ; + }, + }, + { + field: 'availability', + name: AvailabilityLabel, + align: 'right' as const, + render: (val: number) => { + return ( + + + + ); + }, + }, + { + name: LastCheckLabel, + field: 'timestamp', + align: 'right' as const, + }, + ]; + const pageSize = 5; + + const pagination: Pagination = { + pageIndex, + pageSize, + totalItemCount: allLocations.length, + hidePerPageOptions: true, + }; + + const onTableChange = ({ page }: any) => { + setPageIndex(page.index); + }; + + const paginationProps = allLocations.length > pageSize ? { pagination } : {}; + + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/index.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/index.ts new file mode 100644 index 0000000000000..fb42a162522a8 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/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 { AvailabilityReporting } from './availability_reporting'; +export { LocationStatusTags } from './location_status_tags'; +export { TagLabel } from './tag_label'; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx new file mode 100644 index 0000000000000..6096499213a10 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/location_status_tags.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import moment from 'moment'; +import styled from 'styled-components'; +import { UptimeThemeContext } from '../../../../contexts'; +import { MonitorLocation } from '../../../../../common/runtime_types'; +import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../common/constants'; +import { AvailabilityReporting } from '../index'; + +// Set height so that it remains within panel, enough height to display 7 locations tags +const TagContainer = styled.div` + max-height: 246px; + overflow: hidden; +`; + +interface Props { + locations: MonitorLocation[]; +} + +export interface StatusTag { + label: string; + timestamp: string; + color: string; + availability: number; +} + +export const LocationStatusTags = ({ locations }: Props) => { + const { + colors: { gray, danger }, + } = useContext(UptimeThemeContext); + + const allLocations: StatusTag[] = []; + const prevLocal: string = moment.locale() ?? 'en'; + + const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE; + if (!shortLocale) { + moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE); + } + + locations.forEach((item: MonitorLocation) => { + allLocations.push({ + label: item.geo.name!, + timestamp: moment(new Date(item.timestamp).valueOf()).fromNow(), + color: item.summary.down === 0 ? gray : danger, + availability: (item.up_history / (item.up_history + item.down_history)) * 100, + }); + }); + + // Need to reset locale so it doesn't effect other parts of the app + moment.locale(prevLocal); + + // Sort lexicographically by label + allLocations.sort((a, b) => { + return a.label > b.label ? 1 : b.label > a.label ? -1 : 0; + }); + + if (allLocations.length === 0) { + return null; + } + + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx new file mode 100644 index 0000000000000..dbd73fc7d440b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { EuiBadge, EuiText } from '@elastic/eui'; + +const BadgeItem = styled.div` + white-space: nowrap; + display: inline-block; + @media (max-width: 1042px) { + display: inline-block; + margin-right: 16px; + } +`; + +interface Props { + color: string; + label: string; +} + +export const TagLabel: React.FC = ({ color, label }) => { + return ( + + + +

{label}

+
+
+
+ ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/index.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/index.ts similarity index 63% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/index.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/index.ts index e95f14472e9e8..ae3a07d231da3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/index.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/index.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { MonitorStatusBarComponent } from './monitor_status_bar'; export { MonitorStatusDetailsComponent } from './status_details'; -export { StatusByLocations } from './monitor_status_bar/status_by_location'; +export { StatusByLocations } from './status_bar/status_by_location'; export { MonitorStatusDetails } from './status_details_container'; -export { MonitorStatusBar } from './monitor_status_bar/status_bar_container'; +export { MonitorStatusBar } from './status_bar/status_bar'; +export { AvailabilityReporting } from './availability_reporting/availability_reporting'; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/__snapshots__/location_availability.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/__snapshots__/location_availability.test.tsx.snap new file mode 100644 index 0000000000000..94cbeb49a32cf --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/__snapshots__/location_availability.test.tsx.snap @@ -0,0 +1,234 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationAvailability component doesnt shows warning if geo is provided 1`] = ` + + + + + + + + + + + + + +`; + +exports[`LocationAvailability component renders correctly against snapshot 1`] = ` + + + + +

+ Monitoring from +

+
+
+ + + +
+ + + + + +
+`; + +exports[`LocationAvailability component renders named locations that have missing geo data 1`] = ` + + + + + + + + + + + + + + + +`; + +exports[`LocationAvailability component shows warning if geo information is missing 1`] = ` + + + + + + + + + + + + + + + +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_map.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/location_availability.test.tsx similarity index 64% rename from x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_map.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/location_availability.test.tsx index 1913e677dd674..d00cb1f24def8 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_map.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/__tests__/location_availability.test.tsx @@ -6,59 +6,85 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { LocationMap } from '../location_map'; -import { MonitorLocations } from '../../../../../common/runtime_types'; -import { LocationMissingWarning } from '../location_missing'; +import { LocationAvailability } from '../location_availability'; +import { MonitorLocations } from '../../../../../../common/runtime_types'; +import { LocationMissingWarning } from '../../location_map/location_missing'; // Note For shallow test, we need absolute time strings -describe('LocationMap component', () => { +describe('LocationAvailability component', () => { let monitorLocations: MonitorLocations; + let localStorageMock: any; + + let selectedView = 'list'; beforeEach(() => { + localStorageMock = { + getItem: jest.fn().mockImplementation(() => selectedView), + setItem: jest.fn(), + }; + + // @ts-ignore replacing a call to localStorage we use for monitor list size + global.localStorage = localStorageMock; + monitorLocations = { monitorId: 'wapo', + up_history: 12, + down_history: 0, locations: [ { summary: { up: 4, down: 0 }, geo: { name: 'New York', location: { lat: '40.730610', lon: ' -73.935242' } }, timestamp: '2020-01-13T22:50:06.536Z', + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: '2020-01-13T22:50:04.354Z', + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Unnamed-location' }, timestamp: '2020-01-13T22:50:02.753Z', + up_history: 4, + down_history: 0, }, ], }; }); it('renders correctly against snapshot', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); it('shows warning if geo information is missing', () => { + selectedView = 'map'; monitorLocations = { monitorId: 'wapo', + up_history: 8, + down_history: 0, locations: [ { summary: { up: 4, down: 0 }, geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: '2020-01-13T22:50:04.354Z', + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Unnamed-location' }, timestamp: '2020-01-13T22:50:02.753Z', + up_history: 4, + down_history: 0, }, ], }; - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); const warningComponent = component.find(LocationMissingWarning); @@ -68,20 +94,26 @@ describe('LocationMap component', () => { it('doesnt shows warning if geo is provided', () => { monitorLocations = { monitorId: 'wapo', + up_history: 8, + down_history: 0, locations: [ { summary: { up: 4, down: 0 }, geo: { name: 'New York', location: { lat: '40.730610', lon: ' -73.935242' } }, timestamp: '2020-01-13T22:50:06.536Z', + up_history: 4, + down_history: 0, }, { summary: { up: 4, down: 0 }, geo: { name: 'Tokyo', location: { lat: '52.487448', lon: ' 13.394798' } }, timestamp: '2020-01-13T22:50:04.354Z', + up_history: 4, + down_history: 0, }, ], }; - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); const warningComponent = component.find(LocationMissingWarning); @@ -91,16 +123,20 @@ describe('LocationMap component', () => { it('renders named locations that have missing geo data', () => { monitorLocations = { monitorId: 'wapo', + up_history: 4, + down_history: 0, locations: [ { summary: { up: 4, down: 0 }, geo: { name: 'New York', location: undefined }, timestamp: '2020-01-13T22:50:06.536Z', + up_history: 4, + down_history: 0, }, ], }; - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/location_availability.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/location_availability.tsx new file mode 100644 index 0000000000000..89b969fdcf691 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/location_availability.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary, EuiTitle } from '@elastic/eui'; +import { LocationStatusTags } from '../availability_reporting'; +import { LocationPoint } from '../location_map/embeddables/embedded_map'; +import { MonitorLocations, MonitorLocation } from '../../../../../common/runtime_types'; +import { UNNAMED_LOCATION } from '../../../../../common/constants'; +import { LocationMissingWarning } from '../location_map/location_missing'; +import { useSelectedView } from './use_selected_view'; +import { LocationMap } from '../location_map'; +import { MonitoringFrom } from '../translations'; +import { ToggleViewBtn } from './toggle_view_btn'; + +const EuiFlexItemTags = styled(EuiFlexItem)` + width: 350px; + @media (max-width: 1042px) { + width: 100%; + } +`; + +interface LocationMapProps { + monitorLocations: MonitorLocations; +} + +export const LocationAvailability = ({ monitorLocations }: LocationMapProps) => { + const upPoints: LocationPoint[] = []; + const downPoints: LocationPoint[] = []; + + let isAnyGeoInfoMissing = false; + + if (monitorLocations?.locations) { + monitorLocations.locations.forEach(({ geo, summary }: MonitorLocation) => { + if (geo?.name === UNNAMED_LOCATION || !geo?.location) { + isAnyGeoInfoMissing = true; + } else if (!!geo.location.lat && !!geo.location.lon) { + if (summary?.down === 0) { + upPoints.push(geo as LocationPoint); + } else { + downPoints.push(geo as LocationPoint); + } + } + }); + } + const { selectedView: initialView } = useSelectedView(); + + const [selectedView, setSelectedView] = useState(initialView); + + return ( + + + {selectedView === 'list' && ( + + +

{MonitoringFrom}

+
+
+ )} + {selectedView === 'map' && ( + {isAnyGeoInfoMissing && } + )} + + { + setSelectedView(val); + }} + /> + +
+ + + {selectedView === 'list' && ( + + + + )} + {selectedView === 'map' && ( + + + + )} + +
+ ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx new file mode 100644 index 0000000000000..dfce583945cce --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import styled from 'styled-components'; +import { EuiButtonGroup } from '@elastic/eui'; +import { useSelectedView } from './use_selected_view'; +import { ChangeToListView, ChangeToMapView } from '../translations'; + +const ToggleViewButtons = styled.span` + margin-left: auto; +`; + +interface Props { + onChange: (val: string) => void; +} + +export const ToggleViewBtn = ({ onChange }: Props) => { + const toggleButtons = [ + { + id: `listBtn`, + label: ChangeToMapView, + name: 'listView', + iconType: 'list', + 'data-test-subj': 'uptimeMonitorToggleListBtn', + 'aria-label': ChangeToMapView, + }, + { + id: `mapBtn`, + label: ChangeToListView, + name: 'mapView', + iconType: 'mapMarker', + 'data-test-subj': 'uptimeMonitorToggleMapBtn', + 'aria-label': ChangeToListView, + }, + ]; + + const { selectedView, setSelectedView } = useSelectedView(); + + const onChangeView = (optionId: string) => { + const currView = optionId === 'listBtn' ? 'list' : 'map'; + setSelectedView(currView); + onChange(currView); + }; + + return ( + + onChangeView(id)} + type="multi" + isIconOnly + style={{ marginLeft: 'auto' }} + /> + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/use_selected_view.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/use_selected_view.ts new file mode 100644 index 0000000000000..f132f502666f0 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/use_selected_view.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 { useEffect, useState } from 'react'; + +const localKey = 'xpack.uptime.detailPage.selectedView'; + +interface Props { + selectedView: string; + setSelectedView: (val: string) => void; +} + +export const useSelectedView = (): Props => { + const getSelectedView = localStorage.getItem(localKey) ?? 'list'; + + const [selectedView, setSelectedView] = useState(getSelectedView); + + useEffect(() => { + localStorage.setItem(localKey, selectedView); + }, [selectedView]); + + return { selectedView, setSelectedView }; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_map.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_map.test.tsx.snap new file mode 100644 index 0000000000000..6b3d157c23fee --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_map.test.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LocationMap component renders correctly against snapshot 1`] = ` + + + +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap similarity index 99% rename from x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap index 150c4581dfd13..cd282f916f46c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/__snapshots__/location_missing.test.tsx.snap @@ -4,6 +4,7 @@ exports[`LocationMissingWarning component renders correctly against snapshot 1`] .c0 { margin-left: auto; margin-bottom: 3px; + margin-right: 5px; }
{ + let upPoints: LocationPoint[]; + + beforeEach(() => { + upPoints = [ + { + name: 'New York', + location: { lat: '40.730610', lon: ' -73.935242' }, + }, + { + name: 'Tokyo', + location: { lat: '52.487448', lon: ' 13.394798' }, + }, + ]; + }); + + it('renders correctly against snapshot', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_missing.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/location_missing.test.tsx similarity index 100% rename from x-pack/plugins/uptime/public/components/monitor/location_map/__tests__/location_missing.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/__tests__/location_missing.test.tsx diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/__mocks__/mock.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/__mocks__/mock.ts similarity index 96% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/__mocks__/mock.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/__mocks__/mock.ts index 291ab555fbdc6..626011e3d09c9 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/__mocks__/mock.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/__mocks__/mock.ts @@ -14,6 +14,7 @@ export const mockDownPointsLayer = { __featureCollection: { features: [ { + id: 'Asia', type: 'feature', geometry: { type: 'Point', @@ -21,6 +22,7 @@ export const mockDownPointsLayer = { }, }, { + id: 'APJ', type: 'feature', geometry: { type: 'Point', @@ -28,6 +30,7 @@ export const mockDownPointsLayer = { }, }, { + id: 'Canada', type: 'feature', geometry: { type: 'Point', @@ -79,6 +82,7 @@ export const mockUpPointsLayer = { __featureCollection: { features: [ { + id: 'US-EAST', type: 'feature', geometry: { type: 'Point', @@ -86,6 +90,7 @@ export const mockUpPointsLayer = { }, }, { + id: 'US-WEST', type: 'feature', geometry: { type: 'Point', @@ -93,6 +98,7 @@ export const mockUpPointsLayer = { }, }, { + id: 'Europe', type: 'feature', geometry: { type: 'Point', diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/map_config.test.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts similarity index 65% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/map_config.test.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts index 7d53d784ff338..09a41bd9eb4b9 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/__tests__/map_config.test.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/__tests__/map_config.test.ts @@ -7,7 +7,7 @@ import { getLayerList } from '../map_config'; import { mockLayerList } from './__mocks__/mock'; import { LocationPoint } from '../embedded_map'; -import { UptimeAppColors } from '../../../../../uptime_app'; +import { UptimeAppColors } from '../../../../../../uptime_app'; jest.mock('uuid', () => { return { @@ -22,14 +22,14 @@ describe('map_config', () => { beforeEach(() => { upPoints = [ - { lat: '52.487239', lon: '13.399262' }, - { lat: '55.487239', lon: '13.399262' }, - { lat: '54.487239', lon: '14.399262' }, + { name: 'US-EAST', location: { lat: '52.487239', lon: '13.399262' } }, + { location: { lat: '55.487239', lon: '13.399262' }, name: 'US-WEST' }, + { location: { lat: '54.487239', lon: '14.399262' }, name: 'Europe' }, ]; downPoints = [ - { lat: '52.487239', lon: '13.399262' }, - { lat: '55.487239', lon: '13.399262' }, - { lat: '54.487239', lon: '14.399262' }, + { location: { lat: '52.487239', lon: '13.399262' }, name: 'Asia' }, + { location: { lat: '55.487239', lon: '13.399262' }, name: 'APJ' }, + { location: { lat: '54.487239', lon: '14.399262' }, name: 'Canada' }, ]; colors = { danger: '#BC261E', diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx similarity index 68% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx index 06cdb07bd8bcd..648418c02489a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/embedded_map.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/embedded_map.tsx @@ -7,24 +7,30 @@ import React, { useEffect, useState, useContext, useRef } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; -import { MapEmbeddable, MapEmbeddableInput } from '../../../../../../../legacy/plugins/maps/public'; +import { createPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; +import { + MapEmbeddable, + MapEmbeddableInput, +} from '../../../../../../../../legacy/plugins/maps/public'; import * as i18n from './translations'; -import { Location } from '../../../../../common/runtime_types'; +import { GeoPoint } from '../../../../../../common/runtime_types'; import { getLayerList } from './map_config'; -import { UptimeThemeContext, UptimeStartupPluginsContext } from '../../../../contexts'; +import { UptimeThemeContext, UptimeStartupPluginsContext } from '../../../../../contexts'; import { isErrorEmbeddable, ViewMode, ErrorEmbeddable, -} from '../../../../../../../../src/plugins/embeddable/public'; -import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/public'; +} from '../../../../../../../../../src/plugins/embeddable/public'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../../maps/public'; +import { MapToolTipComponent } from './map_tool_tip'; +import { RenderTooltipContentParams } from '../../../../../../../../legacy/plugins/maps/public'; export interface EmbeddedMapProps { upPoints: LocationPoint[]; downPoints: LocationPoint[]; } -export type LocationPoint = Required; +export type LocationPoint = Required; const EmbeddedPanel = styled.div` z-index: auto; @@ -54,6 +60,8 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp } const factory: any = embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); + const portalNode = React.useMemo(() => createPortalNode(), []); + const input: MapEmbeddableInput = { id: uuid.v4(), filters: [], @@ -73,12 +81,38 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp zoom: 0, }, disableInteractive: true, - disableTooltipControl: true, hideToolbarOverlay: true, hideLayerControl: true, hideViewControl: true, }; + const renderTooltipContent = ({ + addFilters, + closeTooltip, + features, + isLocked, + getLayerName, + loadFeatureProperties, + loadFeatureGeometry, + }: RenderTooltipContentParams) => { + const props = { + addFilters, + closeTooltip, + isLocked, + getLayerName, + loadFeatureProperties, + loadFeatureGeometry, + }; + const relevantFeatures = features.filter( + (item: any) => item.layerId === 'up_points' || item.layerId === 'down_points' + ); + if (relevantFeatures.length > 0) { + return ; + } + closeTooltip(); + return null; + }; + useEffect(() => { async function setupEmbeddable() { if (!factory) { @@ -90,11 +124,13 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp }); if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { - embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors)); + embeddableObject.setRenderTooltipContent(renderTooltipContent); + await embeddableObject.setLayerList(getLayerList(upPoints, downPoints, colors)); } setEmbeddable(embeddableObject); } + setupEmbeddable(); // we want this effect to execute exactly once after the component mounts @@ -122,6 +158,9 @@ export const EmbeddedMap = React.memo(({ upPoints, downPoints }: EmbeddedMapProp className="embPanel__content" ref={embeddableRoot} /> + + + ); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/low_poly_layer.json b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/low_poly_layer.json similarity index 100% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/low_poly_layer.json rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/low_poly_layer.json diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/map_config.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts similarity index 94% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/map_config.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts index f1b530c767f1f..e766641102a24 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/map_config.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_config.ts @@ -6,7 +6,7 @@ import lowPolyLayerFeatures from './low_poly_layer.json'; import { LocationPoint } from './embedded_map'; -import { UptimeAppColors } from '../../../../uptime_app'; +import { UptimeAppColors } from '../../../../../uptime_app'; /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, @@ -70,9 +70,10 @@ export const getLowPolyLayer = () => { export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: string) => { const features = downPoints?.map((point) => ({ type: 'feature', + id: point.name, geometry: { type: 'Point', - coordinates: [+point.lon, +point.lat], + coordinates: [+point.location.lon, +point.location.lat], }, })); return { @@ -122,9 +123,10 @@ export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: str export const getUpPointsLayer = (upPoints: LocationPoint[]) => { const features = upPoints?.map((point) => ({ type: 'feature', + id: point.name, geometry: { type: 'Point', - coordinates: [+point.lon, +point.lat], + coordinates: [+point.location.lon, +point.location.lat], }, })); return { diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx new file mode 100644 index 0000000000000..0d54d91007a8d --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/map_tool_tip.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import { + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiOutsideClickDetector, + EuiPopoverTitle, +} from '@elastic/eui'; +import { TagLabel } from '../../availability_reporting'; +import { UptimeThemeContext } from '../../../../../contexts'; +import { AppState } from '../../../../../state'; +import { monitorLocationsSelector } from '../../../../../state/selectors'; +import { useMonitorId } from '../../../../../hooks'; +import { MonitorLocation } from '../../../../../../common/runtime_types/monitor'; +import { RenderTooltipContentParams } from '../../../../../../../../legacy/plugins/maps/public'; +import { formatAvailabilityValue } from '../../availability_reporting/availability_reporting'; +import { LastCheckLabel } from '../../translations'; + +type MapToolTipProps = Partial; + +export const MapToolTipComponent = ({ closeTooltip, features = [] }: MapToolTipProps) => { + const { id: featureId, layerId } = features[0] ?? {}; + const locationName = featureId?.toString(); + const { + colors: { gray, danger }, + } = useContext(UptimeThemeContext); + + const monitorId = useMonitorId(); + + const monitorLocations = useSelector((state: AppState) => + monitorLocationsSelector(state, monitorId) + ); + if (!locationName || !monitorLocations?.locations) { + return null; + } + const { + timestamp, + up_history: ups, + down_history: downs, + }: MonitorLocation = monitorLocations.locations!.find( + ({ geo }: MonitorLocation) => geo.name === locationName + )!; + + const availability = (ups / (ups + downs)) * 100; + + return ( + { + if (closeTooltip != null) { + closeTooltip(); + } + }} + > + <> + + {layerId === 'up_points' ? ( + + ) : ( + + )} + + + Availability + + {i18n.translate('xpack.uptime.mapToolTip.AvailabilityStat.title', { + defaultMessage: '{value} %', + values: { value: formatAvailabilityValue(availability) }, + description: 'A percentage value like 23.5%', + })} + + {LastCheckLabel} + + {moment(timestamp).fromNow()} + + + + + ); +}; + +export const MapToolTip = React.memo(MapToolTipComponent); diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/translations.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/translations.ts similarity index 100% rename from x-pack/plugins/uptime/public/components/monitor/location_map/embeddables/translations.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/embeddables/translations.ts diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/index.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/index.ts similarity index 81% rename from x-pack/plugins/uptime/public/components/monitor/location_map/index.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/index.ts index 140d33bbeef66..4261400bebc6f 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/index.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/index.ts @@ -5,4 +5,4 @@ */ export * from './location_map'; -export * from './location_status_tags'; +export * from '../availability_reporting/location_status_tags'; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_map.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_map.tsx new file mode 100644 index 0000000000000..3d0a097d20d70 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_map.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; + +// These height/width values are used to make sure map is in center of panel +// And to make sure, it doesn't take too much space +const MapPanel = styled.div` + height: 240px; + width: 520px; + margin-right: 65px; + @media (max-width: 574px) { + height: 250px; + width: 100%; + } +`; + +interface Props { + upPoints: LocationPoint[]; + downPoints: LocationPoint[]; +} + +export const LocationMap = ({ upPoints, downPoints }: Props) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/location_map/location_missing.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_missing.tsx similarity index 96% rename from x-pack/plugins/uptime/public/components/monitor/location_map/location_missing.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_missing.tsx index 6ce31e4cc8243..e364b6b8940b3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/location_map/location_missing.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_map/location_missing.tsx @@ -16,11 +16,12 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; -import { LocationLink } from '../../common/location_link'; +import { LocationLink } from '../../../common/location_link'; const EuiPopoverRight = styled(EuiFlexItem)` margin-left: auto; margin-bottom: 3px; + margin-right: 5px; `; export const LocationMissingWarning = () => { diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/index.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/index.ts similarity index 72% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/index.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/index.ts index 3c861412a39e9..22a059d603778 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/index.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/index.ts @@ -5,5 +5,4 @@ */ export { MonitorSSLCertificate } from './ssl_certificate'; -export { MonitorStatusBarComponent } from './status_bar'; -export { MonitorStatusBar } from './status_bar_container'; +export { MonitorStatusBar } from './status_bar'; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/ssl_certificate.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/ssl_certificate.tsx new file mode 100644 index 0000000000000..93720ab313ee3 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/ssl_certificate.tsx @@ -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 React from 'react'; +import { Link } from 'react-router-dom'; +import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Tls } from '../../../../../common/runtime_types'; +import { CERTIFICATES_ROUTE } from '../../../../../common/constants'; +import { MonListDescription, MonListTitle } from './status_bar'; +import { CertStatusColumn } from '../../../overview/monitor_list/cert_status_column'; + +interface Props { + /** + * TLS information coming from monitor in ES heartbeat index + */ + tls: Tls | undefined; +} + +export const MonitorSSLCertificate = ({ tls }: Props) => { + return tls?.not_after ? ( + <> + + + + + + + + + + + + ) : null; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx new file mode 100644 index 0000000000000..afcc8fae7a8ac --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_bar.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { + EuiLink, + EuiIcon, + EuiSpacer, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { MonitorSSLCertificate } from './ssl_certificate'; +import * as labels from '../translations'; +import { StatusByLocations } from './status_by_location'; +import { useStatusBar } from './use_status_bar'; +import { MonitorIDLabel, OverallAvailability } from '../translations'; +import { URL_LABEL } from '../../../common/translations'; +import { MonitorLocations } from '../../../../../common/runtime_types/monitor'; +import { formatAvailabilityValue } from '../availability_reporting/availability_reporting'; + +export const MonListTitle = styled(EuiDescriptionListTitle)` + &&& { + width: 35%; + } +`; + +export const MonListDescription = styled(EuiDescriptionListDescription)` + &&& { + width: 65%; + overflow-wrap: anywhere; + } +`; + +export const MonitorStatusBar: React.FC = () => { + const { monitorId, monitorStatus, monitorLocations = {} } = useStatusBar(); + + const { locations, up_history: ups, down_history: downs } = monitorLocations as MonitorLocations; + + const full = monitorStatus?.url?.full ?? ''; + + const availability = (ups === 0 && downs === 0) || !ups ? 0 : (ups / (ups + downs)) * 100; + + return ( + <> +
+ +
+ + + {OverallAvailability} + + + + {URL_LABEL} + + + {full} + + + {MonitorIDLabel} + {monitorId} + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_by_location.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx similarity index 100% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_by_location.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/status_by_location.tsx diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar_container.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/use_status_bar.ts similarity index 66% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar_container.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/use_status_bar.ts index 9562295437515..27e953aab1b71 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/status_bar_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_bar/use_status_bar.ts @@ -4,24 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, useEffect } from 'react'; +import { useContext, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { monitorLocationsSelector, monitorStatusSelector } from '../../../../state/selectors'; -import { MonitorStatusBarComponent } from './index'; -import { getMonitorStatusAction } from '../../../../state/actions'; -import { useGetUrlParams } from '../../../../hooks'; import { UptimeRefreshContext } from '../../../../contexts'; -import { MonitorIdParam } from '../../../../../common/types'; +import { useGetUrlParams, useMonitorId } from '../../../../hooks'; +import { monitorLocationsSelector, monitorStatusSelector } from '../../../../state/selectors'; import { AppState } from '../../../../state'; +import { getMonitorStatusAction } from '../../../../state/actions'; +import { Ping } from '../../../../../common/runtime_types/ping'; +import { MonitorLocations } from '../../../../../common/runtime_types/monitor'; -export const MonitorStatusBar: React.FC = ({ monitorId }) => { +interface MonitorStatusBarProps { + monitorId: string; + monitorStatus: Ping | null; + monitorLocations?: MonitorLocations; +} + +export const useStatusBar = (): MonitorStatusBarProps => { const { lastRefresh } = useContext(UptimeRefreshContext); const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = useGetUrlParams(); const dispatch = useDispatch(); + const monitorId = useMonitorId(); + const monitorStatus = useSelector(monitorStatusSelector); + const monitorLocations = useSelector((state: AppState) => monitorLocationsSelector(state, monitorId) ); @@ -30,11 +39,5 @@ export const MonitorStatusBar: React.FC = ({ monitorId }) => { dispatch(getMonitorStatusAction({ dateStart, dateEnd, monitorId })); }, [monitorId, dateStart, dateEnd, lastRefresh, dispatch]); - return ( - - ); + return { monitorStatus, monitorLocations, monitorId }; }; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx similarity index 72% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx index 3766ac86785f7..dea39494646a6 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details.tsx @@ -7,31 +7,24 @@ import React, { useContext, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; -import { LocationMap } from '../location_map'; +import { LocationAvailability } from './location_availability/location_availability'; import { UptimeRefreshContext } from '../../../contexts'; import { MonitorLocations } from '../../../../common/runtime_types'; -import { MonitorStatusBar } from './monitor_status_bar'; +import { MonitorStatusBar } from './status_bar'; interface MonitorStatusDetailsProps { - monitorId: string; monitorLocations: MonitorLocations; } const WrapFlexItem = styled(EuiFlexItem)` &&& { - @media (max-width: 768px) { - width: 100%; - } - @media (max-width: 1042px) { - flex-basis: 520px; + @media (max-width: 800px) { + flex-basis: 100%; } } `; -export const MonitorStatusDetailsComponent = ({ - monitorId, - monitorLocations, -}: MonitorStatusDetailsProps) => { +export const MonitorStatusDetailsComponent = ({ monitorLocations }: MonitorStatusDetailsProps) => { const { refreshApp } = useContext(UptimeRefreshContext); const [isTabActive] = useState(document.visibilityState); @@ -56,12 +49,12 @@ export const MonitorStatusDetailsComponent = ({ return ( - - - + + + - - + + diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details_container.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details_container.tsx similarity index 92% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details_container.tsx rename to x-pack/plugins/uptime/public/components/monitor/status_details/status_details_container.tsx index 251f3562f9d1a..92e144bd1b9cc 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/status_details_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_details_container.tsx @@ -28,7 +28,5 @@ export const MonitorStatusDetails: React.FC = ({ monitorId }) => dispatch(getMonitorLocationsAction({ dateStart, dateEnd, monitorId })); }, [monitorId, dateStart, dateEnd, lastRefresh, dispatch]); - return ( - - ); + return ; }; diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/translations.ts b/x-pack/plugins/uptime/public/components/monitor/status_details/translations.ts similarity index 50% rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/translations.ts rename to x-pack/plugins/uptime/public/components/monitor/status_details/translations.ts index f60a1ceeaafb8..f593525fa0942 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/translations.ts +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/translations.ts @@ -48,3 +48,56 @@ export const timestampFromNowTextAriaLabel = i18n.translate( export const loadingMessage = i18n.translate('xpack.uptime.monitorStatusBar.loadingMessage', { defaultMessage: 'Loading…', }); + +export const MonitorIDLabel = i18n.translate('xpack.uptime.monitorStatusBar.monitor.id', { + defaultMessage: 'Monitor ID', +}); + +export const OverallAvailability = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.availability', + { + defaultMessage: 'Overall availability', + } +); + +export const MonitoringFrom = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.monitoringFrom', + { + defaultMessage: 'Monitoring from', + } +); + +export const ChangeToMapView = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.monitoringFrom.listToMap', + { + defaultMessage: 'Change to map view to check availability by location.', + } +); + +export const ChangeToListView = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.monitoringFrom.MapToList', + { + defaultMessage: 'Change to list view to check availability by location.', + } +); + +export const LocationLabel = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.availabilityReport.location', + { + defaultMessage: 'Location', + } +); + +export const AvailabilityLabel = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.availabilityReport.availability', + { + defaultMessage: 'Availability', + } +); + +export const LastCheckLabel = i18n.translate( + 'xpack.uptime.monitorStatusBar.monitor.availabilityReport.lastCheck', + { + defaultMessage: 'Last check', + } +); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx index 5ad0f4d3d1d49..b7c70198912fc 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx @@ -8,13 +8,14 @@ import React from 'react'; import moment from 'moment'; import styled from 'styled-components'; import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; -import { Cert } from '../../../../common/runtime_types'; +import { Cert, Tls } from '../../../../common/runtime_types'; import { useCertStatus } from '../../../hooks'; -import { EXPIRED, EXPIRES_SOON } from '../../certificates/translations'; +import { EXPIRED, EXPIRES, EXPIRES_SOON } from '../../certificates/translations'; import { CERT_STATUS } from '../../../../common/constants'; interface Props { - cert: Cert; + cert: Cert | Tls; + boldStyle?: boolean; } const Span = styled.span` @@ -22,7 +23,15 @@ const Span = styled.span` vertical-align: middle; `; -export const CertStatusColumn: React.FC = ({ cert }) => { +const H4Text = styled.h4` + &&& { + margin: 0 0 0 4px; + display: inline-block; + color: inherit; + } +`; + +export const CertStatusColumn: React.FC = ({ cert, boldStyle = false }) => { const certStatus = useCertStatus(cert?.not_after); const relativeDate = moment(cert?.not_after).fromNow(); @@ -32,9 +41,15 @@ export const CertStatusColumn: React.FC = ({ cert }) => { - - {text} {relativeDate} - + {boldStyle ? ( + + {text} {relativeDate} + + ) : ( + + {text} {relativeDate} + + )} ); @@ -47,5 +62,5 @@ export const CertStatusColumn: React.FC = ({ cert }) => { return ; } - return certStatus ? : --; + return certStatus ? : --; }; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx index 1526838460957..75d587579f66f 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx @@ -30,6 +30,7 @@ import { MonitorListProps } from './monitor_list_container'; import { MonitorList } from '../../../state/reducers/monitor_list'; import { CertStatusColumn } from './cert_status_column'; import { MonitorListHeader } from './monitor_list_header'; +import { URL_LABEL } from '../../common/translations'; interface Props extends MonitorListProps { pageSize: number; @@ -106,7 +107,7 @@ export const MonitorListComponent: React.FC = ({ { align: 'left' as const, field: 'state.url.full', - name: labels.URL, + name: URL_LABEL, render: (url: string, summary: MonitorSummary) => ( {url} diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts index e70eef1d91161..ee922a9ef803f 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts @@ -62,10 +62,6 @@ export const NO_DATA_MESSAGE = i18n.translate('xpack.uptime.monitorList.noItemMe description: 'This message is shown if the monitors table is rendered but has no items.', }); -export const URL = i18n.translate('xpack.uptime.monitorList.table.url.name', { - defaultMessage: 'Url', -}); - export const UP = i18n.translate('xpack.uptime.monitorList.statusColumn.upLabel', { defaultMessage: 'Up', }); diff --git a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap index ec0ee120b6283..fcf68ad97c8ce 100644 --- a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap +++ b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap @@ -2,21 +2,25 @@ exports[`PageHeader shallow renders extra links: page_header_with_extra_links 1`] = ` Array [ - @media only screen and (max-width:1024px) and (min-width:868px) { - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { + .c0 { + white-space: nowrap; +} + +@media only screen and (max-width:1024px) and (min-width:868px) { + .c1.c1.c1 .euiSuperDatePicker__flexWrapper { width: 500px; } } @media only screen and (max-width:880px) { - .c0.c0.c0 { + .c1.c1.c1 { -webkit-box-flex: 1; -webkit-flex-grow: 1; -ms-flex-positive: 1; flex-grow: 1; } - .c0.c0.c0 .euiSuperDatePicker__flexWrapper { + .c1.c1.c1 .euiSuperDatePicker__flexWrapper { width: calc(100% + 8px); } } @@ -28,7 +32,7 @@ Array [ class="euiFlexItem" >

TestingHeading

@@ -100,10 +104,33 @@ Array [
+
+ +

TestingHeading

@@ -250,7 +281,7 @@ Array [ class="euiFlexItem euiFlexItem--flexGrowZero" />

TestingHeading

diff --git a/x-pack/plugins/uptime/public/pages/page_header.tsx b/x-pack/plugins/uptime/public/pages/page_header.tsx index d2e1f9036a24a..421e0e3a4ebde 100644 --- a/x-pack/plugins/uptime/public/pages/page_header.tsx +++ b/x-pack/plugins/uptime/public/pages/page_header.tsx @@ -12,16 +12,22 @@ import styled from 'styled-components'; import { UptimeDatePicker } from '../components/common/uptime_date_picker'; import { SETTINGS_ROUTE } from '../../common/constants'; import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface PageHeaderProps { headingText: string | JSX.Element; extraLinks?: boolean; datePicker?: boolean; } + const SETTINGS_LINK_TEXT = i18n.translate('xpack.uptime.page_header.settingsLink', { defaultMessage: 'Settings', }); +const ADD_DATA_LABEL = i18n.translate('xpack.uptime.addDataButtonLabel', { + defaultMessage: 'Add data', +}); + const StyledPicker = styled(EuiFlexItem)` &&& { @media only screen and (max-width: 1024px) and (min-width: 868px) { @@ -38,6 +44,10 @@ const StyledPicker = styled(EuiFlexItem)` } `; +const H1Text = styled.h1` + white-space: nowrap; +`; + export const PageHeader = React.memo( ({ headingText, extraLinks = false, datePicker = true }: PageHeaderProps) => { const DatePickerComponent = () => @@ -47,6 +57,8 @@ export const PageHeader = React.memo( ) : null; + const kibana = useKibana(); + const extraLinkComponents = !extraLinks ? null : ( @@ -59,6 +71,15 @@ export const PageHeader = React.memo( + + + {ADD_DATA_LABEL} + + ); @@ -73,7 +94,7 @@ export const PageHeader = React.memo( > -

{headingText}

+ {headingText}
{extraLinkComponents} diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts index c8d3ca043edc5..17d79002e6f7d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_locations.ts @@ -32,7 +32,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn< bool: { filter: [ { - match: { + term: { 'monitor.id': monitorId, }, }, @@ -70,6 +70,18 @@ export const getMonitorLocations: UMElasticsearchQueryFn< _source: ['monitor', 'summary', 'observer', '@timestamp'], }, }, + down_history: { + sum: { + field: 'summary.down', + missing: 0, + }, + }, + up_history: { + sum: { + field: 'summary.up', + missing: 0, + }, + }, }, }, }, @@ -99,10 +111,17 @@ export const getMonitorLocations: UMElasticsearchQueryFn< } }; + let totalUps = 0; + let totalDowns = 0; + const monLocs: MonitorLocation[] = []; - locations.forEach((loc: any) => { - const mostRecentLocation = loc.most_recent.hits.hits[0]._source; + locations.forEach(({ most_recent: mostRecent, up_history, down_history }: any) => { + const mostRecentLocation = mostRecent.hits.hits[0]._source; + totalUps += up_history.value; + totalDowns += down_history.value; const location: MonitorLocation = { + up_history: up_history.value ?? 0, + down_history: down_history.value ?? 0, summary: mostRecentLocation?.summary, geo: getGeo(mostRecentLocation?.observer?.geo), timestamp: mostRecentLocation['@timestamp'], @@ -113,5 +132,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn< return { monitorId, locations: monLocs, + up_history: totalUps, + down_history: totalDowns, }; }; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 37b22a687741e..6cafa3eeef08e 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -51,6 +51,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/licensing_plugin/config.legacy.ts'), require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'), require.resolve('../test/reporting_api_integration/config.js'), + require.resolve('../test/functional_embedded/config.ts'), ]; require('@kbn/plugin-helpers').babelRegister(); diff --git a/x-pack/test/accessibility/apps/home.ts b/x-pack/test/accessibility/apps/home.ts index fe698acec322a..1f05ff676e3a0 100644 --- a/x-pack/test/accessibility/apps/home.ts +++ b/x-pack/test/accessibility/apps/home.ts @@ -12,8 +12,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const globalNav = getService('globalNav'); - // FLAKY: https://github.com/elastic/kibana/issues/66976 - describe.skip('Kibana Home', () => { + describe('Kibana Home', () => { before(async () => { await PageObjects.common.navigateToApp('home'); }); diff --git a/x-pack/test/accessibility/apps/search_profiler.ts b/x-pack/test/accessibility/apps/search_profiler.ts index 138231d3cf025..8a13940695f9e 100644 --- a/x-pack/test/accessibility/apps/search_profiler.ts +++ b/x-pack/test/accessibility/apps/search_profiler.ts @@ -14,8 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const a11y = getService('a11y'); const flyout = getService('flyout'); - // FLAKY: https://github.com/elastic/kibana/issues/67821 - describe.skip('Accessibility Search Profiler Editor', () => { + describe('Accessibility Search Profiler Editor', () => { before(async () => { await PageObjects.common.navigateToApp('searchProfiler'); await a11y.testAppSnapshot(); diff --git a/x-pack/test/accessibility/apps/uptime.ts b/x-pack/test/accessibility/apps/uptime.ts index dccce4ba1b0b1..ebd120fa0feea 100644 --- a/x-pack/test/accessibility/apps/uptime.ts +++ b/x-pack/test/accessibility/apps/uptime.ts @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('detail page', async () => { await uptimeService.navigation.goToMonitor(A11Y_TEST_MONITOR_ID); - await uptimeService.monitor.locationMapIsRendered(); + await uptimeService.monitor.displayOverallAvailability('0.00 %'); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts index dc0ccfdc53a18..2a39bc14fbb7f 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts @@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext) => { includes: [], excludes: [], }, - model_memory_limit: '350mb', + model_memory_limit: '60mb', }; const testJobConfigs: Array> = [ @@ -162,7 +162,7 @@ export default ({ getService }: FtrProviderContext) => { }); after(async () => { - await ml.testResources.deleteIndexPattern(destinationIndex); + await ml.testResources.deleteIndexPatternByTitle(destinationIndex); }); it('should delete job and index pattern by id', async () => { @@ -194,7 +194,7 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await ml.api.deleteIndices(destinationIndex); - await ml.testResources.deleteIndexPattern(destinationIndex); + await ml.testResources.deleteIndexPatternByTitle(destinationIndex); }); it('should delete job, target index, and index pattern by id', async () => { diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts index c5761c5dab4cb..d3e4788ee41da 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/get.ts @@ -44,7 +44,7 @@ export default ({ getService }: FtrProviderContext) => { includes: [], excludes: [], }, - model_memory_limit: '350mb', + model_memory_limit: '60mb', }, { id: `${jobId}_2`, @@ -70,7 +70,7 @@ export default ({ getService }: FtrProviderContext) => { includes: [], excludes: [], }, - model_memory_limit: '350mb', + model_memory_limit: '60mb', }, ]; diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 403179ec4ac28..5c2e7a6c4b2f7 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -22,12 +22,28 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.securityCommon.cleanMlUsers(); await ml.securityCommon.cleanMlRoles(); - await ml.testResources.deleteIndexPattern('kibana_sample_data_logs'); - await ml.testResources.deleteIndexPattern('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_apache'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_apm'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_logs'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_nginx'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_logs'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_siem_auditbeat'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_siem_packetbeat'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_siem_winlogbeat'); + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); await esArchiver.unload('ml/ecommerce'); await esArchiver.unload('ml/categorization'); - await esArchiver.unload('ml/sample_logs'); + await esArchiver.unload('ml/module_apache'); + await esArchiver.unload('ml/module_apm'); + await esArchiver.unload('ml/module_logs'); + await esArchiver.unload('ml/module_nginx'); + await esArchiver.unload('ml/module_sample_ecommerce'); + await esArchiver.unload('ml/module_sample_logs'); + await esArchiver.unload('ml/module_siem_auditbeat'); + await esArchiver.unload('ml/module_siem_packetbeat'); + await esArchiver.unload('ml/module_siem_winlogbeat'); await esArchiver.unload('ml/farequote'); await esArchiver.unload('ml/bm_classification'); diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index fda5d1e92e1a6..d217a83efe948 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -19,14 +19,94 @@ export default ({ getService }: FtrProviderContext) => { const testDataList = [ { testTitleSuffix: 'for sample logs dataset', - sourceDataArchive: 'ml/sample_logs', - indexPattern: 'kibana_sample_data_logs', + sourceDataArchive: 'ml/module_sample_logs', + indexPattern: 'ft_module_sample_logs', user: USER.ML_POWERUSER, expected: { responseCode: 200, moduleIds: ['sample_data_weblogs'], }, }, + { + testTitleSuffix: 'for apache dataset', + sourceDataArchive: 'ml/module_apache', + indexPattern: 'ft_module_apache', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['apache_ecs'], + }, + }, + { + testTitleSuffix: 'for apm dataset', + sourceDataArchive: 'ml/module_apm', + indexPattern: 'ft_module_apm', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['apm_jsbase', 'apm_transaction', 'apm_nodejs'], + }, + }, + { + testTitleSuffix: 'for logs dataset', + sourceDataArchive: 'ml/module_logs', + indexPattern: 'ft_module_logs', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: [], // the logs modules don't define a query and can't be recognized + }, + }, + { + testTitleSuffix: 'for nginx dataset', + sourceDataArchive: 'ml/module_nginx', + indexPattern: 'ft_module_nginx', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['nginx_ecs'], + }, + }, + { + testTitleSuffix: 'for sample ecommerce dataset', + sourceDataArchive: 'ml/module_sample_ecommerce', + indexPattern: 'ft_module_sample_ecommerce', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['sample_data_ecommerce'], + }, + }, + { + testTitleSuffix: 'for siem auditbeat dataset', + sourceDataArchive: 'ml/module_siem_auditbeat', + indexPattern: 'ft_module_siem_auditbeat', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['siem_auditbeat', 'siem_auditbeat_auth'], + }, + }, + { + testTitleSuffix: 'for siem packetbeat dataset', + sourceDataArchive: 'ml/module_siem_packetbeat', + indexPattern: 'ft_module_siem_packetbeat', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['siem_packetbeat'], + }, + }, + { + testTitleSuffix: 'for siem winlogbeat dataset', + sourceDataArchive: 'ml/module_siem_winlogbeat', + indexPattern: 'ft_module_siem_winlogbeat', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['siem_winlogbeat'], + }, + }, { testTitleSuffix: 'for non existent index pattern', indexPattern: 'non-existent-index-pattern', @@ -69,8 +149,13 @@ export default ({ getService }: FtrProviderContext) => { ); expect(rspBody).to.be.an(Array); - const responseModuleIds = rspBody.map((module: { id: string }) => module.id); - expect(responseModuleIds).to.eql(testData.expected.moduleIds); + const responseModuleIds = rspBody.map((module: { id: string }) => module.id).sort(); + expect(responseModuleIds).to.eql( + testData.expected.moduleIds.sort(), + `Expected matching module ids for index '${ + testData.indexPattern + }' to be '${testData.expected.moduleIds.sort()}' (got '${responseModuleIds}')` + ); }); }); } diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 981f4b7b24ef2..1c98cd3a4e379 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { isEmpty } from 'lodash'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -22,14 +23,14 @@ export default ({ getService }: FtrProviderContext) => { const testDataListPositive = [ { testTitleSuffix: - 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory false', - sourceDataArchive: 'ml/sample_logs', - indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, + 'for sample_data_weblogs with prefix, startDatafeed false and estimateModelMemory false', + sourceDataArchive: 'ml/module_sample_logs', + indexPattern: { name: 'ft_module_sample_logs', timeField: '@timestamp' }, module: 'sample_data_weblogs', user: USER.ML_POWERUSER, requestBody: { prefix: 'pf1_', - indexPatternName: 'kibana_sample_data_logs', + indexPatternName: 'ft_module_sample_logs', startDatafeed: false, estimateModelMemory: false, }, @@ -55,19 +56,23 @@ export default ({ getService }: FtrProviderContext) => { modelMemoryLimit: '10mb', }, ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], }, }, { testTitleSuffix: - 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory true', - sourceDataArchive: 'ml/sample_logs', - indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, + 'for sample_data_weblogs with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_sample_logs', + indexPattern: { name: 'ft_module_sample_logs', timeField: '@timestamp' }, module: 'sample_data_weblogs', user: USER.ML_POWERUSER, requestBody: { prefix: 'pf2_', - indexPatternName: 'kibana_sample_data_logs', - startDatafeed: false, + indexPatternName: 'ft_module_sample_logs', + startDatafeed: true, + end: Date.now(), }, expected: { responseCode: 200, @@ -91,6 +96,360 @@ export default ({ getService }: FtrProviderContext) => { modelMemoryLimit: '16mb', }, ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for apache_ecs with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_apache', + indexPattern: { name: 'ft_module_apache', timeField: '@timestamp' }, + module: 'apache_ecs', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf3_', + indexPatternName: 'ft_module_apache', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf3_low_request_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf3_source_ip_request_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf3_source_ip_url_count_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '16mb', + }, + { + jobId: 'pf3_status_code_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf3_visitor_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: ['ml_http_access_filebeat_ecs'] as string[], + visualizations: [ + 'ml_http_access_map_ecs', + 'ml_http_access_source_ip_timechart_ecs', + 'ml_http_access_status_code_timechart_ecs', + 'ml_http_access_top_source_ips_table_ecs', + 'ml_http_access_top_urls_table_ecs', + 'ml_http_access_unique_count_url_timechart_ecs', + 'ml_http_access_events_timechart_ecs', + ] as string[], + dashboards: ['ml_http_access_explorer_ecs'] as string[], + }, + }, + { + testTitleSuffix: + 'for apm_nodejs with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_apm', + indexPattern: { name: 'ft_module_apm', timeField: '@timestamp' }, + module: 'apm_nodejs', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf4_', + indexPatternName: 'ft_module_apm', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf4_abnormal_span_durations_nodejs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf4_abnormal_trace_durations_nodejs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf4_decreased_throughput_nodejs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for apm_transaction with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_apm', + indexPattern: { name: 'ft_module_apm', timeField: '@timestamp' }, + module: 'apm_transaction', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf5_', + indexPatternName: 'ft_module_apm', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf5_high_mean_response_time', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for logs_ui_analysis with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_logs', + indexPattern: { name: 'ft_module_logs', timeField: '@timestamp' }, + module: 'logs_ui_analysis', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf6_', + indexPatternName: 'ft_module_logs', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf6_log-entry-rate', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for logs_ui_categories with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_logs', + indexPattern: { name: 'ft_module_logs', timeField: '@timestamp' }, + module: 'logs_ui_categories', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf7_', + indexPatternName: 'ft_module_logs', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf7_log-entry-categories-count', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '26mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: 'for nginx_ecs with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_nginx', + indexPattern: { name: 'ft_module_nginx', timeField: '@timestamp' }, + module: 'nginx_ecs', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf8_', + indexPatternName: 'ft_module_nginx', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf8_visitor_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf8_status_code_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf8_source_ip_url_count_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '16mb', + }, + { + jobId: 'pf8_source_ip_request_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf8_low_request_rate_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: ['ml_http_access_filebeat_ecs'] as string[], + visualizations: [ + 'ml_http_access_map_ecs', + 'ml_http_access_source_ip_timechart_ecs', + 'ml_http_access_status_code_timechart_ecs', + 'ml_http_access_top_source_ips_table_ecs', + 'ml_http_access_top_urls_table_ecs', + 'ml_http_access_unique_count_url_timechart_ecs', + 'ml_http_access_events_timechart_ecs', + ] as string[], + dashboards: ['ml_http_access_explorer_ecs'] as string[], + }, + }, + { + testTitleSuffix: + 'for sample_data_ecommerce with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_sample_ecommerce', + indexPattern: { name: 'ft_module_sample_ecommerce', timeField: 'order_date' }, + module: 'sample_data_ecommerce', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf9_', + indexPatternName: 'ft_module_sample_ecommerce', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf9_high_sum_total_sales', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for siem_auditbeat_auth with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_siem_auditbeat', + indexPattern: { name: 'ft_module_siem_auditbeat', timeField: '@timestamp' }, + module: 'siem_auditbeat_auth', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf11_', + indexPatternName: 'ft_module_siem_auditbeat', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf11_suspicious_login_activity_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for siem_packetbeat with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_siem_packetbeat', + indexPattern: { name: 'ft_module_siem_packetbeat', timeField: '@timestamp' }, + module: 'siem_packetbeat', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf12_', + indexPatternName: 'ft_module_siem_packetbeat', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf12_packetbeat_dns_tunneling', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '16mb', + }, + { + jobId: 'pf12_packetbeat_rare_dns_question', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf12_packetbeat_rare_server_domain', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf12_packetbeat_rare_urls', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf12_packetbeat_rare_user_agent', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], }, }, ]; @@ -113,13 +472,13 @@ export default ({ getService }: FtrProviderContext) => { }, { testTitleSuffix: 'for unauthorized user', - sourceDataArchive: 'ml/sample_logs', - indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, + sourceDataArchive: 'ml/module_sample_logs', + indexPattern: { name: 'ft_module_sample_logs', timeField: '@timestamp' }, module: 'sample_data_weblogs', user: USER.ML_UNAUTHORIZED, requestBody: { - prefix: 'pf1_', - indexPatternName: 'kibana_sample_data_logs', + prefix: 'pfn1_', + indexPatternName: 'ft_module_sample_logs', startDatafeed: false, }, expected: { @@ -156,6 +515,16 @@ export default ({ getService }: FtrProviderContext) => { return 0; } + function mapIdsToSuccessObjects(ids: string[]) { + const successObjects = ids + .map((id) => { + return { id, success: true }; + }) + .sort(compareById); + + return successObjects; + } + describe('module setup', function () { before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); @@ -172,6 +541,15 @@ export default ({ getService }: FtrProviderContext) => { }); after(async () => { + for (const search of testData.expected.searches) { + await ml.testResources.deleteSavedSearchById(search); + } + for (const visualization of testData.expected.visualizations) { + await ml.testResources.deleteVisualizationById(visualization); + } + for (const dashboard of testData.expected.dashboards) { + await ml.testResources.deleteDashboardById(dashboard); + } await ml.api.cleanMlIndices(); }); @@ -188,11 +566,8 @@ export default ({ getService }: FtrProviderContext) => { // jobs expect(rspBody).to.have.property('jobs'); - const expectedRspJobs = testData.expected.jobs - .map((job) => { - return { id: job.jobId, success: true }; - }) - .sort(compareById); + const expectedJobIds = testData.expected.jobs.map((job) => job.jobId); + const expectedRspJobs = mapIdsToSuccessObjects(expectedJobIds); const actualRspJobs = rspBody.jobs.sort(compareById); @@ -225,7 +600,42 @@ export default ({ getService }: FtrProviderContext) => { )}' (got '${JSON.stringify(actualRspDatafeeds)}')` ); - // TODO in future updates: add response validations for created saved objects + // saved objects + const rspKibana: object = rspBody.kibana; + let actualSearches = []; + let actualVisualizations = []; + let actualDashboards = []; + + if (isEmpty(rspKibana) === false) { + actualSearches = rspBody.kibana.search.sort(compareById); + actualVisualizations = rspBody.kibana.visualization.sort(compareById); + actualDashboards = rspBody.kibana.dashboard.sort(compareById); + } + + const expectedSearches = mapIdsToSuccessObjects(testData.expected.searches); + const expectedVisualizations = mapIdsToSuccessObjects(testData.expected.visualizations); + const expectedDashboards = mapIdsToSuccessObjects(testData.expected.dashboards); + + expect(actualSearches).to.eql( + expectedSearches, + `Expected setup module response searches to be '${JSON.stringify( + expectedSearches + )}' (got '${JSON.stringify(actualSearches)}')` + ); + + expect(actualVisualizations).to.eql( + expectedVisualizations, + `Expected setup module response visualizations to be '${JSON.stringify( + expectedVisualizations + )}' (got '${JSON.stringify(actualVisualizations)}')` + ); + + expect(actualDashboards).to.eql( + expectedDashboards, + `Expected setup module response dashboards to be '${JSON.stringify( + expectedDashboards + )}' (got '${JSON.stringify(actualDashboards)}')` + ); } // verify job and datafeed creation + states @@ -271,9 +681,18 @@ export default ({ getService }: FtrProviderContext) => { expectedModelMemoryLimits )}' (got '${JSON.stringify(actualModelMemoryLimits)}')` ); - }); - // TODO in future updates: add creation validations for created saved objects + // verify saved objects creation + for (const search of testData.expected.searches) { + await ml.testResources.assertSavedSearchExistById(search); + } + for (const visualization of testData.expected.visualizations) { + await ml.testResources.assertVisualizationExistById(visualization); + } + for (const dashboard of testData.expected.dashboards) { + await ml.testResources.assertDashboardExistById(dashboard); + } + }); }); } diff --git a/x-pack/test/api_integration/apis/transform/delete_transforms.ts b/x-pack/test/api_integration/apis/transform/delete_transforms.ts index 40300c981ee2e..8e5d7354bcaf4 100644 --- a/x-pack/test/api_integration/apis/transform/delete_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/delete_transforms.ts @@ -247,7 +247,7 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await transform.api.deleteIndices(destinationIndex); - await transform.testResources.deleteIndexPattern(destinationIndex); + await transform.testResources.deleteIndexPatternByTitle(destinationIndex); }); it('should delete transform and destination index pattern', async () => { @@ -287,7 +287,7 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await transform.api.deleteIndices(destinationIndex); - await transform.testResources.deleteIndexPattern(destinationIndex); + await transform.testResources.deleteIndexPatternByTitle(destinationIndex); }); it('should delete transform, destination index, & destination index pattern', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index 6ff5a8c552ab7..cc6fa53939f60 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -14,6 +14,7 @@ import { deleteSignalsIndex, deleteAllRulesStatuses, getSimpleRule, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -51,7 +52,17 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // wait for Task Manager to execute the rule and update status - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [resBody.id] }) + .expect(200); + return ( + body[resBody.id].current_status?.status === 'succeeded' || + body[resBody.id].current_status?.status === 'going to run' + ); + }); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts index 66962df14e22d..e0b60ae1fbeeb 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/import_rules.ts @@ -17,6 +17,7 @@ import { getSimpleRuleOutput, removeServerGeneratedProperties, ruleToNdjson, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -33,8 +34,12 @@ export default ({ getService }: FtrProviderContext): void => { .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(400); - // We have to wait up to 5 seconds for any unresolved promises to flush - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) + .send(); + return body.status_code === 404; + }); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts index fe7783fa2d045..93dbc2471ae18 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/open_close_signals.ts @@ -6,18 +6,31 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../plugins/security_solution/common/constants'; +import { SearchResponse } from 'elasticsearch'; +import { Signal } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; +import { + DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_QUERY_SIGNALS_URL, +} from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, + getSimpleRule, + waitFor, + getQueryAllSignals, + getQuerySignalIds, + deleteAllAlerts, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); describe('open_close_signals', () => { describe('validation checks', () => { @@ -50,5 +63,231 @@ export default ({ getService }: FtrProviderContext) => { await deleteSignalsIndex(supertest); }); }); + + describe('tests with auditbeat data', () => { + beforeEach(async () => { + await deleteAllAlerts(es); + await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); + }); + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + await esArchiver.unload('auditbeat/hosts'); + }); + + it('should be able to execute and get 10 signals', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get the collection of signals + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + // expect there to be 10 + expect(signalsOpen.hits.hits.length).equal(10); + }); + + it('should be have set the signals in an open state initially', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get the collection of signals + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const everySignalOpen = signalsOpen.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'open' + ); + + // expect their initial state to all be open + expect(everySignalOpen).to.eql(true); + }); + + it('should be able to get a count of 10 closed signals when closing 10', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get a collection of signals to get the id to set open/closed on them + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // set all of the signals to the state of closed. There is no reason to use a waitUntil here + // as this route intentionally has a waitFor within it and should only return when the query has + // the data. + await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(200); + + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + expect(signalsClosed.hits.hits.length).to.equal(10); + }); + + it('should be able close 10 signals immediately and they all should be closed', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get a collection of signals to get the id to set open/closed on them + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // set all of the signals to the state of closed. There is no reason to use a waitUntil here + // as this route intentionally has a waitFor within it and should only return when the query has + // the data. + await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(200); + + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + const everySignalClosed = signalsClosed.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'closed' + ); + expect(everySignalClosed).to.eql(true); + }); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index 8847b0c3250ac..c763be1c2c3ec 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -23,6 +23,7 @@ import { removeServerGeneratedPropertiesIncludingRuleId, getSimpleMlRule, getSimpleMlRuleOutput, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -96,7 +97,15 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // wait for Task Manager to execute the rule and update status - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body.id] }) + .expect(200); + return statusBody[body.id].current_status?.status === 'succeeded'; + }); + const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 47b09fd1fe3c7..52865e43be750 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -21,6 +21,7 @@ import { getSimpleRuleWithoutRuleId, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -99,7 +100,14 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // wait for Task Manager to execute the rule and update status - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body[0].id] }) + .expect(200); + return statusBody[body[0].id].current_status?.status === 'succeeded'; + }); const { body: statusBody } = await supertest .post(DETECTION_ENGINE_RULES_STATUS_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index f659857b89a97..aee8d400be905 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -14,6 +14,7 @@ import { deleteSignalsIndex, deleteAllRulesStatuses, getSimpleRule, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -70,7 +71,14 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // wait for Task Manager to execute the rule and update status - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .set('kbn-xsrf', 'true') + .send({ ids: [resBody.id] }) + .expect(200); + return body[resBody.id].current_status?.status === 'succeeded'; + }); // query the single rule from _find const { body } = await supertest diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts index 66962df14e22d..e0b60ae1fbeeb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts @@ -17,6 +17,7 @@ import { getSimpleRuleOutput, removeServerGeneratedProperties, ruleToNdjson, + waitFor, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -33,8 +34,12 @@ export default ({ getService }: FtrProviderContext): void => { .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') .expect(400); - // We have to wait up to 5 seconds for any unresolved promises to flush - await new Promise((resolve) => setTimeout(resolve, 5000)); + await waitFor(async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) + .send(); + return body.status_code === 404; + }); // Try to fetch the rule which should still be a 404 (not found) const { body } = await supertest.get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`).send(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index fe7783fa2d045..6b924eddfdc44 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -6,18 +6,31 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../plugins/security_solution/common/constants'; +import { SearchResponse } from 'elasticsearch'; +import { Signal } from '../../../../plugins/security_solution/server/lib/detection_engine/signals/types'; +import { + DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_QUERY_SIGNALS_URL, +} from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, deleteSignalsIndex, setSignalStatus, getSignalStatusEmptyResponse, + getSimpleRule, + waitFor, + getQueryAllSignals, + getQuerySignalIds, + deleteAllAlerts, } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); describe('open_close_signals', () => { describe('validation checks', () => { @@ -49,6 +62,210 @@ export default ({ getService }: FtrProviderContext) => { await deleteSignalsIndex(supertest); }); + + describe('tests with auditbeat data', () => { + beforeEach(async () => { + await deleteAllAlerts(es); + await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); + }); + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + await esArchiver.unload('auditbeat/hosts'); + }); + + it('should be able to execute and get 10 signals', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get the collection of signals + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + // expect there to be 10 + expect(signalsOpen.hits.hits.length).equal(10); + }); + + it('should be have set the signals in an open state initially', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get the collection of signals + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const everySignalOpen = signalsOpen.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'open' + ); + + // expect their initial state to all be open + expect(everySignalOpen).to.eql(true); + }); + + it('should be able to get a count of 10 closed signals when closing 10', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get a collection of signals to get the id to set open/closed on them + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // set all of the signals to the state of closed. There is no reason to use a waitUntil here + // as this route intentionally has a waitFor within it and should only return when the query has + // the data. + await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(200); + + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + expect(signalsClosed.hits.hits.length).to.equal(10); + }); + + it('should be able close 10 signals immediately and they all should be closed', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + + // create a simple rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rule) + .expect(200); + + // wait until all the rules show up and are present + await waitFor(async () => { + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + return signalsOpen.hits.hits.length === 10; + }); + + // Get a collection of signals to get the id to set open/closed on them + const { + body: signalsOpen, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQueryAllSignals()) + .expect(200); + + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // set all of the signals to the state of closed. There is no reason to use a waitUntil here + // as this route intentionally has a waitFor within it and should only return when the query has + // the data. + await supertest + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(200); + + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + const everySignalClosed = signalsClosed.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'closed' + ); + expect(everySignalClosed).to.eql(true); + }); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index b58089e30e930..816df9c133ea1 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -7,6 +7,10 @@ import { Client } from '@elastic/elasticsearch'; import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; +import { + Status, + SignalIds, +} from '../../plugins/security_solution/common/detection_engine/schemas/common/schemas'; import { CreateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema'; import { UpdateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema'; import { RulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema'; @@ -106,12 +110,24 @@ export const getSignalStatus = () => ({ aggs: { statuses: { terms: { field: 'signal.status', size: 10 } } }, }); +export const getQueryAllSignals = () => ({ + query: { match_all: {} }, +}); + +export const getQuerySignalIds = (signalIds: SignalIds) => ({ + query: { + terms: { + _id: signalIds, + }, + }, +}); + export const setSignalStatus = ({ signalIds, status, }: { - signalIds: string[]; - status: 'open' | 'closed'; + signalIds: SignalIds; + status: Status; }) => ({ signal_ids: signalIds, status, @@ -457,3 +473,29 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => query: 'user.name: root or user.name: admin', exceptions_list: [], }); + +// Similar to ReactJs's waitFor from here: https://testing-library.com/docs/dom-testing-library/api-async#waitfor +export const waitFor = async ( + functionToTest: () => Promise, + maxTimeout: number = 5000, + timeoutWait: number = 10 +) => { + await new Promise(async (resolve, reject) => { + let found = false; + let numberOfTries = 0; + while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { + const itPasses = await functionToTest(); + if (itPasses) { + found = true; + } else { + numberOfTries++; + } + await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait)); + } + if (found) { + resolve(); + } else { + reject(new Error('timed out waiting for function condition to be true')); + } + }); +}; diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts index 2c63baf7c75cb..bcdd3d1f82e7d 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/dashboard_drilldowns.ts @@ -7,9 +7,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -const DASHBOARD_WITH_PIE_CHART_NAME = 'Dashboard with Pie Chart'; -const DASHBOARD_WITH_AREA_CHART_NAME = 'Dashboard With Area Chart'; - const DRILLDOWN_TO_PIE_CHART_NAME = 'Go to pie chart dashboard'; const DRILLDOWN_TO_AREA_CHART_NAME = 'Go to area chart dashboard'; @@ -18,8 +15,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); const dashboardDrilldownsManage = getService('dashboardDrilldownsManage'); const PageObjects = getPageObjects(['dashboard', 'common', 'header', 'timePicker']); - const kibanaServer = getService('kibanaServer'); - const esArchiver = getService('esArchiver'); const pieChart = getService('pieChart'); const log = getService('log'); const browser = getService('browser'); @@ -30,19 +25,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Dashboard Drilldowns', function () { before(async () => { log.debug('Dashboard Drilldowns:initTests'); - await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.load('dashboard/drilldowns'); - await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); }); - after(async () => { - await esArchiver.unload('dashboard/drilldowns'); - }); - it('should create dashboard to dashboard drilldown, use it, and then delete it', async () => { - await PageObjects.dashboard.gotoDashboardEditMode(DASHBOARD_WITH_PIE_CHART_NAME); + await PageObjects.dashboard.gotoDashboardEditMode( + dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME + ); // create drilldown await dashboardPanelActions.openContextMenu(); @@ -51,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutOpen(); await dashboardDrilldownsManage.fillInDashboardToDashboardDrilldownWizard({ drilldownName: DRILLDOWN_TO_AREA_CHART_NAME, - destinationDashboardTitle: DASHBOARD_WITH_AREA_CHART_NAME, + destinationDashboardTitle: dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME, }); await dashboardDrilldownsManage.saveChanges(); await dashboardDrilldownsManage.expectsCreateDrilldownFlyoutClose(); @@ -60,10 +50,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.dashboard.getPanelDrilldownCount()).to.be(1); // save dashboard, navigate to view mode - await PageObjects.dashboard.saveDashboard(DASHBOARD_WITH_PIE_CHART_NAME, { - saveAsNew: false, - waitDialogIsClosed: true, - }); + await PageObjects.dashboard.saveDashboard( + dashboardDrilldownsManage.DASHBOARD_WITH_PIE_CHART_NAME, + { + saveAsNew: false, + waitDialogIsClosed: true, + } + ); // trigger drilldown action by clicking on a pie and picking drilldown action by it's name await pieChart.filterOnPieSlice('40,000'); @@ -117,7 +110,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('browser back/forward navigation works after drilldown navigation', async () => { - await PageObjects.dashboard.loadSavedDashboard(DASHBOARD_WITH_AREA_CHART_NAME); + await PageObjects.dashboard.loadSavedDashboard( + dashboardDrilldownsManage.DASHBOARD_WITH_AREA_CHART_NAME + ); const originalTimeRangeDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); await brushAreaChart(); await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts new file mode 100644 index 0000000000000..24d6e820ac0eb --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/drilldowns/explore_data_panel_action.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const ACTION_ID = 'ACTION_EXPLORE_DATA'; +const EXPLORE_RAW_DATA_ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const drilldowns = getService('dashboardDrilldownsManage'); + const { dashboard, discover, common, timePicker } = getPageObjects([ + 'dashboard', + 'discover', + 'common', + 'timePicker', + ]); + const panelActions = getService('dashboardPanelActions'); + const panelActionsTimeRange = getService('dashboardPanelTimeRange'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + + describe('Explore underlying data - panel action', function () { + before(async () => { + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash*' }); + await common.navigateToApp('dashboard'); + await dashboard.preserveCrossAppState(); + }); + + after(async () => { + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + }); + + it('action exists in panel context menu', async () => { + await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); + await panelActions.openContextMenu(); + await testSubjects.existOrFail(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + }); + + it('is a link element', async () => { + const actionElement = await testSubjects.find(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + const tag = await actionElement.getTagName(); + + expect(tag.toLowerCase()).to.be('a'); + }); + + it('navigates to Discover app to index pattern of the panel on action click', async () => { + await testSubjects.clickWhenNotDisabled(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + await discover.waitForDiscoverAppOnScreen(); + + const el = await testSubjects.find('indexPattern-switch-link'); + const text = await el.getVisibleText(); + + expect(text).to.be('logstash-*'); + }); + + it('carries over panel time range', async () => { + await common.navigateToApp('dashboard'); + + await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); + + await panelActions.openContextMenu(); + await panelActionsTimeRange.clickTimeRangeActionInContextMenu(); + await panelActionsTimeRange.clickToggleQuickMenuButton(); + await panelActionsTimeRange.clickCommonlyUsedTimeRange('Last_90 days'); + await panelActionsTimeRange.clickModalPrimaryButton(); + + await dashboard.saveDashboard('Dashboard with Pie Chart'); + + await panelActions.openContextMenu(); + await testSubjects.clickWhenNotDisabled(EXPLORE_RAW_DATA_ACTION_TEST_SUBJ); + await discover.waitForDiscoverAppOnScreen(); + + const text = await timePicker.getShowDatesButtonText(); + const lowercaseText = text.toLowerCase(); + + expect(lowercaseText.includes('last 90 days')).to.be(true); + }); + }); +} diff --git a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts index d1081b053a8b9..19d85ad0e448f 100644 --- a/x-pack/test/functional/apps/dashboard/drilldowns/index.ts +++ b/x-pack/test/functional/apps/dashboard/drilldowns/index.ts @@ -5,9 +5,24 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + describe('drilldowns', function () { this.tags(['skipFirefox']); + + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('dashboard/drilldowns'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + }); + + after(async () => { + await esArchiver.unload('dashboard/drilldowns'); + }); + loadTestFile(require.resolve('./dashboard_drilldowns')); + loadTestFile(require.resolve('./explore_data_panel_action')); }); } diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index e926bcd6ef997..353902f4265a0 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -11,22 +11,6 @@ const PIPELINE = { name: 'test_pipeline', description: 'My pipeline description.', version: 1, - processors: JSON.stringify([ - { - set: { - field: 'foo', - value: 'new', - }, - }, - ]), - onFailureProcessors: JSON.stringify([ - { - set: { - field: '_index', - value: 'failed-{{ _index }}', - }, - }, - ]), }; export default ({ getPageObjects, getService }: FtrProviderContext) => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts index 56d00a4e11390..c23abead458f1 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/anomaly_explorer.ts @@ -61,6 +61,7 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.createMLTestDashboardIfNeeded(); await ml.testResources.setKibanaTimeZoneToUTC(); await ml.securityUI.loginAsMlPowerUser(); @@ -125,6 +126,12 @@ export default function ({ getService }: FtrProviderContext) { it('anomalies table is not empty', async () => { await ml.anomaliesTable.assertTableNotEmpty(); }); + + // should be the last step because it navigates away from the Anomaly Explorer page + it('should allow to attach anomaly swimlane embeddable to the dashboard', async () => { + await ml.anomalyExplorer.openAddToDashboardControl(); + await ml.anomalyExplorer.addAndEditSwimlaneInDashboard('ML Test'); + }); }); } }); diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts index b0376b35ebd2f..578d7eada554e 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/classification_creation.ts @@ -37,7 +37,7 @@ export default function ({ getService }: FtrProviderContext) { }, dependentVariable: 'y', trainingPercent: '20', - modelMemory: '200mb', + modelMemory: '60mb', createIndexPattern: true, expected: { row: { @@ -52,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { describe(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); - await ml.testResources.deleteIndexPattern(testData.destinationIndex); + await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); it('loads the data frame analytics page', async () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts index b3e47db826787..357ea36213521 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts @@ -53,7 +53,7 @@ export default function ({ getService }: FtrProviderContext) { includes: [], excludes: [], }, - model_memory_limit: '350mb', + model_memory_limit: '60mb', allow_lazy_start: false, }, }, @@ -83,7 +83,7 @@ export default function ({ getService }: FtrProviderContext) { includes: [], excludes: [], }, - model_memory_limit: '55mb', + model_memory_limit: '5mb', }, }, { @@ -115,7 +115,7 @@ export default function ({ getService }: FtrProviderContext) { includes: [], excludes: [], }, - model_memory_limit: '105mb', + model_memory_limit: '20mb', }, }, ]; @@ -153,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { after(async () => { await ml.api.deleteIndices(cloneDestIndex); await ml.api.deleteIndices(testData.job.dest!.index as string); - await ml.testResources.deleteIndexPattern(testData.job.dest!.index as string); + await ml.testResources.deleteIndexPatternByTitle(testData.job.dest!.index as string); }); it('should open the flyout with a proper header', async () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 2daae60b0ad20..582b19f5e18a8 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -34,7 +34,7 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.jobId}`; }, - modelMemory: '55mb', + modelMemory: '5mb', createIndexPattern: true, expected: { row: { @@ -50,7 +50,7 @@ export default function ({ getService }: FtrProviderContext) { describe(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); - await ml.testResources.deleteIndexPattern(testData.destinationIndex); + await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); it('loads the data frame analytics page', async () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 35425e47526dd..fea87e70eb014 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -36,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { }, dependentVariable: 'stab', trainingPercent: '20', - modelMemory: '105mb', + modelMemory: '20mb', createIndexPattern: true, expected: { row: { @@ -52,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { describe(`${testData.suiteTitle}`, function () { after(async () => { await ml.api.deleteIndices(testData.destinationIndex); - await ml.testResources.deleteIndexPattern(testData.destinationIndex); + await ml.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); it('loads the data frame analytics page', async () => { diff --git a/x-pack/test/functional/apps/ml/index.ts b/x-pack/test/functional/apps/ml/index.ts index 685fff4506155..2d8aac3b8dddf 100644 --- a/x-pack/test/functional/apps/ml/index.ts +++ b/x-pack/test/functional/apps/ml/index.ts @@ -22,14 +22,15 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.securityCommon.cleanMlRoles(); await ml.testResources.deleteSavedSearches(); - - await ml.testResources.deleteIndexPattern('ft_farequote'); - await ml.testResources.deleteIndexPattern('ft_ecommerce'); - await ml.testResources.deleteIndexPattern('ft_categorization'); - await ml.testResources.deleteIndexPattern('ft_event_rate_gen_trend_nanos'); - await ml.testResources.deleteIndexPattern('ft_bank_marketing'); - await ml.testResources.deleteIndexPattern('ft_ihp_outlier'); - await ml.testResources.deleteIndexPattern('ft_egs_regression'); + await ml.testResources.deleteDashboards(); + + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await ml.testResources.deleteIndexPatternByTitle('ft_categorization'); + await ml.testResources.deleteIndexPatternByTitle('ft_event_rate_gen_trend_nanos'); + await ml.testResources.deleteIndexPatternByTitle('ft_bank_marketing'); + await ml.testResources.deleteIndexPatternByTitle('ft_ihp_outlier'); + await ml.testResources.deleteIndexPatternByTitle('ft_egs_regression'); await esArchiver.unload('ml/farequote'); await esArchiver.unload('ml/ecommerce'); diff --git a/x-pack/test/functional/apps/transform/cloning.ts b/x-pack/test/functional/apps/transform/cloning.ts index d28ac50eefe95..b7e71860bcb56 100644 --- a/x-pack/test/functional/apps/transform/cloning.ts +++ b/x-pack/test/functional/apps/transform/cloning.ts @@ -39,7 +39,7 @@ export default function ({ getService }: FtrProviderContext) { }); after(async () => { - await transform.testResources.deleteIndexPattern(transformConfig.dest.index); + await transform.testResources.deleteIndexPatternByTitle(transformConfig.dest.index); await transform.api.deleteIndices(transformConfig.dest.index); await transform.api.cleanTransformIndices(); }); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index d6dbcde436dcc..61c4776e20404 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -240,7 +240,7 @@ export default function ({ getService }: FtrProviderContext) { describe(`${testData.suiteTitle}`, function () { after(async () => { await transform.api.deleteIndices(testData.destinationIndex); - await transform.testResources.deleteIndexPattern(testData.destinationIndex); + await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); it('loads the home page', async () => { diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index f8de38a1e3afd..ad62f06d1f3cd 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -76,7 +76,7 @@ export default function ({ getService }: FtrProviderContext) { describe(`${testData.suiteTitle}`, function () { after(async () => { await transform.api.deleteIndices(testData.destinationIndex); - await transform.testResources.deleteIndexPattern(testData.destinationIndex); + await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); }); it('loads the home page', async () => { diff --git a/x-pack/test/functional/apps/transform/index.ts b/x-pack/test/functional/apps/transform/index.ts index dcca156454f35..a7859be6923d5 100644 --- a/x-pack/test/functional/apps/transform/index.ts +++ b/x-pack/test/functional/apps/transform/index.ts @@ -23,8 +23,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await transform.testResources.deleteSavedSearches(); - await transform.testResources.deleteIndexPattern('ft_farequote'); - await transform.testResources.deleteIndexPattern('ft_ecommerce'); + await transform.testResources.deleteIndexPatternByTitle('ft_farequote'); + await transform.testResources.deleteIndexPatternByTitle('ft_ecommerce'); await esArchiver.unload('ml/farequote'); await esArchiver.unload('ml/ecommerce'); diff --git a/x-pack/test/functional/apps/uptime/locations.ts b/x-pack/test/functional/apps/uptime/locations.ts index 27c42a365ec74..8aefca6a70195 100644 --- a/x-pack/test/functional/apps/uptime/locations.ts +++ b/x-pack/test/functional/apps/uptime/locations.ts @@ -11,41 +11,58 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const { uptime: uptimePage } = getPageObjects(['uptime']); const uptime = getService('uptime'); + const es = getService('legacyEs'); const monitor = () => uptime.monitor; + const MONITOR_ID = 'location-testing-id'; + + const LessAvailMonitor = 'less-availability-monitor'; + + const addMonitorWithNoLocation = async () => { + /** + * This mogrify function will strip the documents of their location + * data (but preserve their location name), which is necessary for + * this test to work as desired. + * @param d current document + */ + const mogrifyNoLocation = (d: any) => { + if (d.observer?.geo?.location) { + d.observer.geo.location = undefined; + } + return d; + }; + await makeChecksWithStatus(es, MONITOR_ID, 5, 2, 10000, {}, 'up', mogrifyNoLocation); + }; + + const addLessAvailMonitor = async () => { + await makeChecksWithStatus(es, LessAvailMonitor, 5, 2, 10000, {}, 'up'); + await makeChecksWithStatus(es, LessAvailMonitor, 5, 2, 10000, {}, 'down'); + }; describe('Observer location', () => { const start = moment().subtract('15', 'm').toISOString(); const end = moment().toISOString(); - const MONITOR_ID = 'location-testing-id'; + before(async () => { + await addMonitorWithNoLocation(); + await addLessAvailMonitor(); + await uptime.navigation.goToUptime(); + await uptimePage.goToRoot(true); + }); beforeEach(async () => { - /** - * This mogrify function will strip the documents of their location - * data (but preserve their location name), which is necessary for - * this test to work as desired. - * @param d current document - */ - const mogrifyNoLocation = (d: any) => { - if (d.observer?.geo?.location) { - d.observer.geo.location = undefined; - } - return d; - }; - await makeChecksWithStatus( - getService('legacyEs'), - MONITOR_ID, - 5, - 2, - 10000, - {}, - 'up', - mogrifyNoLocation - ); - await uptime.navigation.goToUptime(); + await addMonitorWithNoLocation(); + await addLessAvailMonitor(); + if (!(await uptime.navigation.isOnDetailsPage())) + await uptimePage.loadDataAndGoToMonitorPage(start, end, MONITOR_ID); + }); + + it('displays the overall availability', async () => { + await monitor().displayOverallAvailability('100.00 %'); + }); - await uptimePage.loadDataAndGoToMonitorPage(start, end, MONITOR_ID); + it('can change the view to map', async () => { + await monitor().toggleToMapView(); }); it('renders the location panel and canvas', async () => { @@ -55,5 +72,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the location missing popover when monitor has location name, but no geo data', async () => { await monitor().locationMissingExists(); }); + + it('displays less monitor availability', async () => { + await uptime.navigation.goToHomeViaBreadCrumb(); + await uptimePage.loadDataAndGoToMonitorPage(start, end, LessAvailMonitor); + await monitor().displayOverallAvailability('50.00 %'); + }); }); }; diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json b/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json new file mode 100644 index 0000000000000..220daaeb61d0e --- /dev/null +++ b/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json @@ -0,0 +1,263 @@ +{ + "type": "doc", + "value": { + "id": "space:default", + "index": ".kibana", + "source": { + "space": { + "description": "This is the default space!", + "name": "Default" + }, + "type": "space" + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization☺漢字-DataTable", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "DataTable", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization☺漢字 DataTable", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"table\",\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMeticsAtAllLevels\":false},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"histogram\",\"schema\":\"bucket\",\"params\":{\"field\":\"bytes\",\"interval\":2000,\"extended_bounds\":{}}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization☺-VerticalBarChart", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "VerticalBarChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization☺ VerticalBarChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"histogram\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"scale\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization-TileMap", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "TileMap", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization TileMap", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"tile_map\",\"params\":{\"mapType\":\"Scaled Circle Markers\",\"isDesaturated\":true,\"addTooltip\":true,\"heatMaxZoom\":16,\"heatMinOpacity\":0.1,\"heatRadius\":25,\"heatBlur\":15,\"heatNormalizeData\":true,\"wms\":{\"enabled\":false,\"url\":\"https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WMSServer\",\"options\":{\"version\":\"1.3.0\",\"layers\":\"0\",\"format\":\"image/png\",\"transparent\":true,\"attribution\":\"Maps provided by USGS\",\"styles\":\"\"}}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"geohash_grid\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.coordinates\",\"autoPrecision\":true,\"precision\":2}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "index-pattern:logstash-*", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "type": "index-pattern" + } + } +} + +{ + "type": "doc", + "value": { + "id": "index-pattern:logstash*", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "timeFieldName": "@timestamp", + "title": "logstash*" + }, + "type": "index-pattern" + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization-PieChart", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "PieChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization PieChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"pie\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"isDonut\":false},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"memory\",\"interval\":40000,\"extended_bounds\":{}}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization漢字-LineChart", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "LineChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization漢字 LineChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"line\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"showCircles\":true,\"smoothLines\":false,\"interpolate\":\"linear\",\"scale\":\"linear\",\"drawLinesBetweenPoints\":true,\"radiusRatio\":9,\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"extension.raw\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":false}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization-MetricChart", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "MetricChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization MetricChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"metric\",\"params\":{\"handleNoResults\":true,\"fontSize\":60},\"aggs\":[{\"id\":\"1\",\"type\":\"percentile_ranks\",\"schema\":\"metric\",\"params\":{\"field\":\"memory\",\"values\":[99]}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "visualization:Visualization漢字-AreaChart", + "index": ".kibana", + "source": { + "type": "visualization", + "visualization": { + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "title": "Visualization漢字 AreaChart", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}" + } + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:24f3f950-69d9-11ea-a14d-e341629a29e6", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"7.7.0\",\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"e637d5f0-a7e6-4635-81ed-39f2b1aac6f4\"},\"panelIndex\":\"e637d5f0-a7e6-4635-81ed-39f2b1aac6f4\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"ffd3e4dc-cb1a-419f-afeb-03e8c7742bbf\",\"triggers\":[\"VALUE_CLICK_TRIGGER\",\"SELECT_RANGE_TRIGGER\"],\"action\":{\"name\":\"Go to pie chart dashboard\",\"config\":{\"dashboardId\":\"41e77910-69d9-11ea-a14d-e341629a29e6\",\"useCurrentDateRange\":true,\"useCurrentFilters\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title": "Dashboard With Area Chart", + "version": 1 + }, + "references": [ + { + "id": "Visualization漢字-AreaChart", + "name": "panel_0", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "dashboard:41e77910-69d9-11ea-a14d-e341629a29e6", + "index": ".kibana", + "source": { + "dashboard": { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"7.7.0\",\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"8c5df6b2-0cc9-4887-a2d9-6a9a192f3407\"},\"panelIndex\":\"8c5df6b2-0cc9-4887-a2d9-6a9a192f3407\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"}]", + "refreshInterval": { "pause": true, "value": 0 }, + "timeFrom": "2015-09-19T17:34:10.297Z", + "timeRestore": true, + "timeTo": "2015-09-23T00:09:17.180Z", + "title": "Dashboard with Pie Chart", + "version": 1 + }, + "references": [ + { + "id": "Visualization-PieChart", + "name": "panel_0", + "type": "visualization" + } + ], + "type": "dashboard", + "updated_at": "2020-03-19T11:59:53.701Z" + } + } +} + + diff --git a/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz b/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz deleted file mode 100644 index a9b23ca7a579b..0000000000000 Binary files a/x-pack/test/functional/es_archives/dashboard/drilldowns/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/ml/module_apache/data.json.gz b/x-pack/test/functional/es_archives/ml/module_apache/data.json.gz new file mode 100644 index 0000000000000..ac72c6e06eabc Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_apache/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_apache/mappings.json b/x-pack/test/functional/es_archives/ml/module_apache/mappings.json new file mode 100644 index 0000000000000..5fe0d5f221b4e --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_apache/mappings.json @@ -0,0 +1,2671 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_apache", + "mappings": { + "_meta": { + "beat": "filebeat", + "version": "7.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kibana.log.meta": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "kibana.log.meta.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "apache": { + "properties": { + "access": { + "properties": { + "agent": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssl": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "apache2": { + "properties": { + "access": { + "properties": { + "geoip": { + "type": "object" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "type": "object" + } + } + }, + "auditd": { + "properties": { + "log": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "type": "ip" + }, + "geoip": { + "type": "object" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "type": "ip" + }, + "lport": { + "type": "long" + }, + "new_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "type": "long" + }, + "sequence": { + "type": "long" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "certificate": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "audit": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "indices": { + "ignore_above": 1024, + "type": "keyword" + }, + "layer": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "request": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "params": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "deprecation": { + "type": "object" + }, + "gc": { + "properties": { + "heap": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "jvm_runtime_sec": { + "type": "float" + }, + "old_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "phase": { + "properties": { + "class_unload_time_sec": { + "type": "float" + }, + "cpu_time": { + "properties": { + "real_sec": { + "type": "float" + }, + "sys_sec": { + "type": "float" + }, + "user_sec": { + "type": "float" + } + } + }, + "duration_sec": { + "type": "float" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parallel_rescan_time_sec": { + "type": "float" + }, + "scrub_string_table_time_sec": { + "type": "float" + }, + "scrub_symbol_table_time_sec": { + "type": "float" + }, + "weak_refs_processing_time_sec": { + "type": "float" + } + } + }, + "stopping_threads_time_sec": { + "type": "float" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threads_total_stop_time_sec": { + "type": "float" + }, + "young_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + } + } + }, + "index": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "gc": { + "properties": { + "collection_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "observation_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "overhead_seq": { + "type": "long" + }, + "young": { + "properties": { + "one": { + "type": "long" + }, + "two": { + "type": "long" + } + } + } + } + } + } + }, + "shard": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "extra_source": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "routing": { + "ignore_above": 1024, + "type": "keyword" + }, + "search_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "source_query": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "ignore_above": 1024, + "type": "keyword" + }, + "took": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_hits": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_shards": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "types": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fileset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "haproxy": { + "properties": { + "backend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "backend_queue": { + "type": "long" + }, + "bind_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes_read": { + "type": "long" + }, + "client": { + "type": "object" + }, + "connection_wait_time_ms": { + "type": "long" + }, + "connections": { + "properties": { + "active": { + "type": "long" + }, + "backend": { + "type": "long" + }, + "frontend": { + "type": "long" + }, + "retries": { + "type": "long" + }, + "server": { + "type": "long" + } + } + }, + "destination": { + "type": "object" + }, + "error_message": { + "norms": false, + "type": "text" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "http": { + "properties": { + "request": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_request_line": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_wait_ms": { + "type": "long" + }, + "time_wait_without_data_ms": { + "type": "long" + } + } + }, + "response": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_queue": { + "type": "long" + }, + "source": { + "norms": false, + "type": "text" + }, + "tcp": { + "properties": { + "connection_waiting_time_ms": { + "type": "long" + } + } + }, + "termination_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_backend_connect": { + "type": "long" + }, + "time_queue": { + "type": "long" + }, + "total_waiting_time_ms": { + "type": "long" + } + } + }, + "hash": { + "properties": { + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "icinga": { + "properties": { + "debug": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "main": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "startup": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "iis": { + "properties": { + "access": { + "properties": { + "cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "site_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sub_status": { + "type": "long" + }, + "user_agent": { + "type": "object" + }, + "win32_status": { + "type": "long" + } + } + }, + "error": { + "properties": { + "geoip": { + "type": "object" + }, + "queue_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason_phrase": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "input": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kafka": { + "properties": { + "log": { + "properties": { + "class": { + "norms": false, + "type": "text" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "trace": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "norms": false, + "type": "text" + }, + "message": { + "norms": false, + "type": "text" + } + } + } + } + } + } + }, + "kibana": { + "properties": { + "log": { + "properties": { + "meta": { + "type": "object" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "type": "object" + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "type": "long" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "logstash": { + "properties": { + "log": { + "properties": { + "log_event": { + "type": "object" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "event": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params_object": { + "type": "object" + }, + "plugin_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "took_in_millis": { + "type": "long" + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "mongodb": { + "properties": { + "log": { + "properties": { + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "context": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mysql": { + "properties": { + "error": { + "type": "object" + }, + "slowlog": { + "properties": { + "bytes_sent": { + "type": "long" + }, + "current_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesort": { + "type": "boolean" + }, + "filesort_on_disk": { + "type": "boolean" + }, + "full_join": { + "type": "boolean" + }, + "full_scan": { + "type": "boolean" + }, + "innodb": { + "properties": { + "io_r_bytes": { + "type": "long" + }, + "io_r_ops": { + "type": "long" + }, + "io_r_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "pages_distinct": { + "type": "long" + }, + "queue_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "rec_lock_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "trx_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "killed": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_errno": { + "ignore_above": 1024, + "type": "keyword" + }, + "lock_time": { + "properties": { + "sec": { + "type": "float" + } + } + }, + "log_slow_rate_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "log_slow_rate_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "merge_passes": { + "type": "long" + }, + "priority_queue": { + "type": "boolean" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "query_cache_hit": { + "type": "boolean" + }, + "rows_affected": { + "type": "long" + }, + "rows_examined": { + "type": "long" + }, + "rows_sent": { + "type": "long" + }, + "schema": { + "ignore_above": 1024, + "type": "keyword" + }, + "tmp_disk_tables": { + "type": "long" + }, + "tmp_table": { + "type": "boolean" + }, + "tmp_table_on_disk": { + "type": "boolean" + }, + "tmp_table_sizes": { + "type": "long" + }, + "tmp_tables": { + "type": "long" + } + } + }, + "thread_id": { + "type": "long" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nginx": { + "properties": { + "access": { + "properties": { + "geoip": { + "type": "object" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "properties": { + "connection_id": { + "type": "long" + } + } + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "osquery": { + "properties": { + "result": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "calendar_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "host_identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "unix_time": { + "type": "long" + } + } + } + } + }, + "postgresql": { + "properties": { + "log": { + "properties": { + "core_id": { + "type": "long" + }, + "database": { + "ignore_above": 1024, + "type": "keyword" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "program": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "redis": { + "properties": { + "log": { + "properties": { + "role": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "santa": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "decision": { + "ignore_above": 1024, + "type": "keyword" + }, + "disk": { + "properties": { + "bsdname": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "mount": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial": { + "ignore_above": 1024, + "type": "keyword" + }, + "volume": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "stream": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "type": "long" + }, + "facility_label": { + "ignore_above": 1024, + "type": "keyword" + }, + "priority": { + "type": "long" + }, + "severity_label": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "auth": { + "properties": { + "groupadd": { + "type": "object" + }, + "ssh": { + "properties": { + "dropped_ip": { + "type": "ip" + }, + "geoip": { + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "sudo": { + "properties": { + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "error": { + "ignore_above": 1024, + "type": "keyword" + }, + "pwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "useradd": { + "properties": { + "home": { + "ignore_above": 1024, + "type": "keyword" + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "syslog": { + "type": "object" + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "traefik": { + "properties": { + "access": { + "properties": { + "backend_url": { + "ignore_above": 1024, + "type": "keyword" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "properties": { + "city_name": { + "path": "source.geo.city_name", + "type": "alias" + }, + "continent_name": { + "path": "source.geo.continent_name", + "type": "alias" + }, + "country_iso_code": { + "path": "source.geo.country_iso_code", + "type": "alias" + }, + "location": { + "path": "source.geo.location", + "type": "alias" + }, + "region_iso_code": { + "path": "source.geo.region_iso_code", + "type": "alias" + }, + "region_name": { + "path": "source.geo.region_name", + "type": "alias" + } + } + }, + "request_count": { + "type": "long" + }, + "user_agent": { + "properties": { + "device": { + "path": "user_agent.device.name", + "type": "alias" + }, + "major": { + "path": "user_agent.major", + "type": "alias" + }, + "minor": { + "path": "user_agent.minor", + "type": "alias" + }, + "name": { + "path": "user_agent.name", + "type": "alias" + }, + "original": { + "path": "user_agent.original", + "type": "alias" + }, + "os": { + "path": "user_agent.os.full_name", + "type": "alias" + }, + "os_major": { + "path": "user_agent.os.major", + "type": "alias" + }, + "os_minor": { + "path": "user_agent.os.minor", + "type": "alias" + }, + "os_name": { + "path": "user_agent.os.name", + "type": "alias" + }, + "patch": { + "path": "user_agent.patch", + "type": "alias" + } + } + }, + "user_identifier": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "patch": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_apm/data.json.gz b/x-pack/test/functional/es_archives/ml/module_apm/data.json.gz new file mode 100644 index 0000000000000..72edb0e071e1c Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_apm/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_apm/mappings.json b/x-pack/test/functional/es_archives/ml/module_apm/mappings.json new file mode 100644 index 0000000000000..872b3e1ddd83d --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_apm/mappings.json @@ -0,0 +1,379 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_apm", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sampled": { + "type": "boolean" + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_logs/data.json.gz b/x-pack/test/functional/es_archives/ml/module_logs/data.json.gz new file mode 100644 index 0000000000000..761f57c999626 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_logs/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_logs/mappings.json b/x-pack/test/functional/es_archives/ml/module_logs/mappings.json new file mode 100644 index 0000000000000..b727ef2b3c48d --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_logs/mappings.json @@ -0,0 +1,5584 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_logs", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "apache": { + "properties": { + "access": { + "properties": { + "ssl": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "apache2": { + "properties": { + "access": { + "properties": { + "geoip": { + "type": "object" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "type": "object" + } + } + }, + "auditd": { + "properties": { + "log": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "type": "ip" + }, + "geoip": { + "type": "object" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "type": "ip" + }, + "lport": { + "type": "long" + }, + "new_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "type": "long" + }, + "sequence": { + "type": "long" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "certificate": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "audit": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "event_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "indices": { + "ignore_above": 1024, + "type": "keyword" + }, + "layer": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "request": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "params": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "deprecation": { + "type": "object" + }, + "gc": { + "properties": { + "heap": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "jvm_runtime_sec": { + "type": "float" + }, + "old_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "phase": { + "properties": { + "class_unload_time_sec": { + "type": "float" + }, + "cpu_time": { + "properties": { + "real_sec": { + "type": "float" + }, + "sys_sec": { + "type": "float" + }, + "user_sec": { + "type": "float" + } + } + }, + "duration_sec": { + "type": "float" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parallel_rescan_time_sec": { + "type": "float" + }, + "scrub_string_table_time_sec": { + "type": "float" + }, + "scrub_symbol_table_time_sec": { + "type": "float" + }, + "weak_refs_processing_time_sec": { + "type": "float" + } + } + }, + "stopping_threads_time_sec": { + "type": "float" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threads_total_stop_time_sec": { + "type": "float" + }, + "young_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + } + } + }, + "index": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "gc": { + "properties": { + "collection_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "observation_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "overhead_seq": { + "type": "long" + }, + "young": { + "properties": { + "one": { + "type": "long" + }, + "two": { + "type": "long" + } + } + } + } + }, + "stacktrace": { + "ignore_above": 1024, + "index": false, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "extra_source": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "routing": { + "ignore_above": 1024, + "type": "keyword" + }, + "search_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "source_query": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "ignore_above": 1024, + "type": "keyword" + }, + "took": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_hits": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_shards": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "types": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fileset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "haproxy": { + "properties": { + "backend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "backend_queue": { + "type": "long" + }, + "bind_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes_read": { + "type": "long" + }, + "client": { + "type": "object" + }, + "connection_wait_time_ms": { + "type": "long" + }, + "connections": { + "properties": { + "active": { + "type": "long" + }, + "backend": { + "type": "long" + }, + "frontend": { + "type": "long" + }, + "retries": { + "type": "long" + }, + "server": { + "type": "long" + } + } + }, + "destination": { + "type": "object" + }, + "error_message": { + "norms": false, + "type": "text" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "http": { + "properties": { + "request": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_request_line": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_wait_ms": { + "type": "long" + }, + "time_wait_without_data_ms": { + "type": "long" + } + } + }, + "response": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_queue": { + "type": "long" + }, + "source": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcp": { + "properties": { + "connection_waiting_time_ms": { + "type": "long" + } + } + }, + "termination_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_backend_connect": { + "type": "long" + }, + "time_queue": { + "type": "long" + }, + "total_waiting_time_ms": { + "type": "long" + } + } + }, + "hash": { + "properties": { + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "icinga": { + "properties": { + "debug": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "main": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "startup": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "iis": { + "properties": { + "access": { + "properties": { + "cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "site_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sub_status": { + "type": "long" + }, + "user_agent": { + "type": "object" + }, + "win32_status": { + "type": "long" + } + } + }, + "error": { + "properties": { + "geoip": { + "type": "object" + }, + "queue_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason_phrase": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "input": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "iptables": { + "properties": { + "ether_type": { + "type": "long" + }, + "flow_label": { + "type": "long" + }, + "fragment_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment_offset": { + "type": "long" + }, + "icmp": { + "properties": { + "code": { + "type": "long" + }, + "id": { + "type": "long" + }, + "parameter": { + "type": "long" + }, + "redirect": { + "type": "ip" + }, + "seq": { + "type": "long" + }, + "type": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "incomplete_bytes": { + "type": "long" + }, + "input_device": { + "ignore_above": 1024, + "type": "keyword" + }, + "length": { + "type": "long" + }, + "output_device": { + "ignore_above": 1024, + "type": "keyword" + }, + "precedence_bits": { + "type": "short" + }, + "tcp": { + "properties": { + "ack": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "reserved_bits": { + "type": "short" + }, + "seq": { + "type": "long" + }, + "window": { + "type": "long" + } + } + }, + "tos": { + "type": "long" + }, + "ttl": { + "type": "long" + }, + "ubiquiti": { + "properties": { + "input_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "output_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "rule_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "rule_set": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "udp": { + "properties": { + "length": { + "type": "long" + } + } + } + } + }, + "kafka": { + "properties": { + "log": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "trace": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + } + } + } + } + } + } + }, + "kibana": { + "properties": { + "log": { + "properties": { + "meta": { + "type": "object" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "type": "object" + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "type": "long" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "logstash": { + "properties": { + "log": { + "properties": { + "log_event": { + "type": "object" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "event": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params_object": { + "type": "object" + }, + "plugin_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "took_in_millis": { + "type": "long" + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "mongodb": { + "properties": { + "log": { + "properties": { + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "context": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mysql": { + "properties": { + "error": { + "type": "object" + }, + "slowlog": { + "properties": { + "bytes_sent": { + "type": "long" + }, + "current_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesort": { + "type": "boolean" + }, + "filesort_on_disk": { + "type": "boolean" + }, + "full_join": { + "type": "boolean" + }, + "full_scan": { + "type": "boolean" + }, + "innodb": { + "properties": { + "io_r_bytes": { + "type": "long" + }, + "io_r_ops": { + "type": "long" + }, + "io_r_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "pages_distinct": { + "type": "long" + }, + "queue_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "rec_lock_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "trx_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "killed": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_errno": { + "ignore_above": 1024, + "type": "keyword" + }, + "lock_time": { + "properties": { + "sec": { + "type": "float" + } + } + }, + "log_slow_rate_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "log_slow_rate_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "merge_passes": { + "type": "long" + }, + "priority_queue": { + "type": "boolean" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "query_cache_hit": { + "type": "boolean" + }, + "rows_affected": { + "type": "long" + }, + "rows_examined": { + "type": "long" + }, + "rows_sent": { + "type": "long" + }, + "schema": { + "ignore_above": 1024, + "type": "keyword" + }, + "tmp_disk_tables": { + "type": "long" + }, + "tmp_table": { + "type": "boolean" + }, + "tmp_table_on_disk": { + "type": "boolean" + }, + "tmp_table_sizes": { + "type": "long" + }, + "tmp_tables": { + "type": "long" + } + } + }, + "thread_id": { + "type": "long" + } + } + }, + "netflow": { + "properties": { + "absolute_error": { + "type": "double" + }, + "address_pool_high_threshold": { + "type": "long" + }, + "address_pool_low_threshold": { + "type": "long" + }, + "address_port_mapping_high_threshold": { + "type": "long" + }, + "address_port_mapping_low_threshold": { + "type": "long" + }, + "address_port_mapping_per_user_high_threshold": { + "type": "long" + }, + "anonymization_flags": { + "type": "long" + }, + "anonymization_technique": { + "type": "long" + }, + "application_category_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_description": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_group_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_id": { + "type": "short" + }, + "application_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_sub_category_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "bgp_destination_as_number": { + "type": "long" + }, + "bgp_next_adjacent_as_number": { + "type": "long" + }, + "bgp_next_hop_ipv4_address": { + "type": "ip" + }, + "bgp_next_hop_ipv6_address": { + "type": "ip" + }, + "bgp_prev_adjacent_as_number": { + "type": "long" + }, + "bgp_source_as_number": { + "type": "long" + }, + "bgp_validity_state": { + "type": "short" + }, + "biflow_direction": { + "type": "short" + }, + "class_id": { + "type": "short" + }, + "class_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification_engine_id": { + "type": "short" + }, + "collection_time_milliseconds": { + "type": "date" + }, + "collector_certificate": { + "type": "short" + }, + "collector_ipv4_address": { + "type": "ip" + }, + "collector_ipv6_address": { + "type": "ip" + }, + "collector_transport_port": { + "type": "long" + }, + "common_properties_id": { + "type": "long" + }, + "confidence_level": { + "type": "double" + }, + "connection_sum_duration_seconds": { + "type": "long" + }, + "connection_transaction_id": { + "type": "long" + }, + "data_link_frame_section": { + "type": "short" + }, + "data_link_frame_size": { + "type": "long" + }, + "data_link_frame_type": { + "type": "long" + }, + "data_records_reliability": { + "type": "boolean" + }, + "delta_flow_count": { + "type": "long" + }, + "destination_ipv4_address": { + "type": "ip" + }, + "destination_ipv4_prefix": { + "type": "ip" + }, + "destination_ipv4_prefix_length": { + "type": "short" + }, + "destination_ipv6_address": { + "type": "ip" + }, + "destination_ipv6_prefix": { + "type": "ip" + }, + "destination_ipv6_prefix_length": { + "type": "short" + }, + "destination_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "destination_transport_port": { + "type": "long" + }, + "digest_hash_value": { + "type": "long" + }, + "distinct_count_of_destinatio_nipa_ddress": { + "type": "long" + }, + "distinct_count_of_destination_ipv4_address": { + "type": "long" + }, + "distinct_count_of_destination_ipv6_address": { + "type": "long" + }, + "distinct_count_of_sourc_eipa_ddress": { + "type": "long" + }, + "distinct_count_of_source_ipv4_address": { + "type": "long" + }, + "distinct_count_of_source_ipv6_address": { + "type": "long" + }, + "dot1q_customer_dei": { + "type": "boolean" + }, + "dot1q_customer_destination_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "dot1q_customer_priority": { + "type": "short" + }, + "dot1q_customer_source_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "dot1q_customer_vlan_id": { + "type": "long" + }, + "dot1q_dei": { + "type": "boolean" + }, + "dot1q_priority": { + "type": "short" + }, + "dot1q_service_instance_id": { + "type": "long" + }, + "dot1q_service_instance_priority": { + "type": "short" + }, + "dot1q_service_instance_tag": { + "type": "short" + }, + "dot1q_vlan_id": { + "type": "long" + }, + "dropped_layer2_octet_delta_count": { + "type": "long" + }, + "dropped_layer2_octet_total_count": { + "type": "long" + }, + "dropped_octet_delta_count": { + "type": "long" + }, + "dropped_octet_total_count": { + "type": "long" + }, + "dropped_packet_delta_count": { + "type": "long" + }, + "dropped_packet_total_count": { + "type": "long" + }, + "dst_traffic_index": { + "type": "long" + }, + "egress_broadcast_packet_total_count": { + "type": "long" + }, + "egress_interface": { + "type": "long" + }, + "egress_interface_type": { + "type": "long" + }, + "egress_physical_interface": { + "type": "long" + }, + "egress_unicast_packet_total_count": { + "type": "long" + }, + "egress_vrfid": { + "type": "long" + }, + "encrypted_technology": { + "ignore_above": 1024, + "type": "keyword" + }, + "engine_id": { + "type": "short" + }, + "engine_type": { + "type": "short" + }, + "ethernet_header_length": { + "type": "short" + }, + "ethernet_payload_length": { + "type": "long" + }, + "ethernet_total_length": { + "type": "long" + }, + "ethernet_type": { + "type": "long" + }, + "export_interface": { + "type": "long" + }, + "export_protocol_version": { + "type": "short" + }, + "export_sctp_stream_id": { + "type": "long" + }, + "export_transport_protocol": { + "type": "short" + }, + "exported_flow_record_total_count": { + "type": "long" + }, + "exported_message_total_count": { + "type": "long" + }, + "exported_octet_total_count": { + "type": "long" + }, + "exporter": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "source_id": { + "type": "long" + }, + "timestamp": { + "type": "date" + }, + "uptime_millis": { + "type": "long" + }, + "version": { + "type": "long" + } + } + }, + "exporter_certificate": { + "type": "short" + }, + "exporter_ipv4_address": { + "type": "ip" + }, + "exporter_ipv6_address": { + "type": "ip" + }, + "exporter_transport_port": { + "type": "long" + }, + "exporting_process_id": { + "type": "long" + }, + "external_address_realm": { + "type": "short" + }, + "firewall_event": { + "type": "short" + }, + "flags_and_sampler_id": { + "type": "long" + }, + "flow_active_timeout": { + "type": "long" + }, + "flow_direction": { + "type": "short" + }, + "flow_duration_microseconds": { + "type": "long" + }, + "flow_duration_milliseconds": { + "type": "long" + }, + "flow_end_delta_microseconds": { + "type": "long" + }, + "flow_end_microseconds": { + "type": "date" + }, + "flow_end_milliseconds": { + "type": "date" + }, + "flow_end_nanoseconds": { + "type": "date" + }, + "flow_end_reason": { + "type": "short" + }, + "flow_end_seconds": { + "type": "date" + }, + "flow_end_sys_up_time": { + "type": "long" + }, + "flow_id": { + "type": "long" + }, + "flow_idle_timeout": { + "type": "long" + }, + "flow_key_indicator": { + "type": "long" + }, + "flow_label_ipv6": { + "type": "long" + }, + "flow_sampling_time_interval": { + "type": "long" + }, + "flow_sampling_time_spacing": { + "type": "long" + }, + "flow_selected_flow_delta_count": { + "type": "long" + }, + "flow_selected_octet_delta_count": { + "type": "long" + }, + "flow_selected_packet_delta_count": { + "type": "long" + }, + "flow_selector_algorithm": { + "type": "long" + }, + "flow_start_delta_microseconds": { + "type": "long" + }, + "flow_start_microseconds": { + "type": "date" + }, + "flow_start_milliseconds": { + "type": "date" + }, + "flow_start_nanoseconds": { + "type": "date" + }, + "flow_start_seconds": { + "type": "date" + }, + "flow_start_sys_up_time": { + "type": "long" + }, + "forwarding_status": { + "type": "short" + }, + "fragment_flags": { + "type": "short" + }, + "fragment_identification": { + "type": "long" + }, + "fragment_offset": { + "type": "long" + }, + "global_address_mapping_high_threshold": { + "type": "long" + }, + "gre_key": { + "type": "long" + }, + "hash_digest_output": { + "type": "boolean" + }, + "hash_flow_domain": { + "type": "long" + }, + "hash_initialiser_value": { + "type": "long" + }, + "hash_ipp_ayload_offset": { + "type": "long" + }, + "hash_ipp_ayload_size": { + "type": "long" + }, + "hash_output_range_max": { + "type": "long" + }, + "hash_output_range_min": { + "type": "long" + }, + "hash_selected_range_max": { + "type": "long" + }, + "hash_selected_range_min": { + "type": "long" + }, + "http_content_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_message_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_reason_phrase": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_request_host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_request_method": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_request_target": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_status_code": { + "type": "long" + }, + "http_user_agent": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_code_ipv4": { + "type": "short" + }, + "icmp_code_ipv6": { + "type": "short" + }, + "icmp_type_code_ipv4": { + "type": "long" + }, + "icmp_type_code_ipv6": { + "type": "long" + }, + "icmp_type_ipv4": { + "type": "short" + }, + "icmp_type_ipv6": { + "type": "short" + }, + "igmp_type": { + "type": "short" + }, + "ignored_data_record_total_count": { + "type": "long" + }, + "ignored_layer2_frame_total_count": { + "type": "long" + }, + "ignored_layer2_octet_total_count": { + "type": "long" + }, + "ignored_octet_total_count": { + "type": "long" + }, + "ignored_packet_total_count": { + "type": "long" + }, + "information_element_data_type": { + "type": "short" + }, + "information_element_description": { + "ignore_above": 1024, + "type": "keyword" + }, + "information_element_id": { + "type": "long" + }, + "information_element_index": { + "type": "long" + }, + "information_element_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "information_element_range_begin": { + "type": "long" + }, + "information_element_range_end": { + "type": "long" + }, + "information_element_semantics": { + "type": "short" + }, + "information_element_units": { + "type": "long" + }, + "ingress_broadcast_packet_total_count": { + "type": "long" + }, + "ingress_interface": { + "type": "long" + }, + "ingress_interface_type": { + "type": "long" + }, + "ingress_multicast_packet_total_count": { + "type": "long" + }, + "ingress_physical_interface": { + "type": "long" + }, + "ingress_unicast_packet_total_count": { + "type": "long" + }, + "ingress_vrfid": { + "type": "long" + }, + "initiator_octets": { + "type": "long" + }, + "initiator_packets": { + "type": "long" + }, + "interface_description": { + "ignore_above": 1024, + "type": "keyword" + }, + "interface_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "intermediate_process_id": { + "type": "long" + }, + "internal_address_realm": { + "type": "short" + }, + "ip_class_of_service": { + "type": "short" + }, + "ip_diff_serv_code_point": { + "type": "short" + }, + "ip_header_length": { + "type": "short" + }, + "ip_header_packet_section": { + "type": "short" + }, + "ip_next_hop_ipv4_address": { + "type": "ip" + }, + "ip_next_hop_ipv6_address": { + "type": "ip" + }, + "ip_payload_length": { + "type": "long" + }, + "ip_payload_packet_section": { + "type": "short" + }, + "ip_precedence": { + "type": "short" + }, + "ip_sec_spi": { + "type": "long" + }, + "ip_total_length": { + "type": "long" + }, + "ip_ttl": { + "type": "short" + }, + "ip_version": { + "type": "short" + }, + "ipv4_ihl": { + "type": "short" + }, + "ipv4_options": { + "type": "long" + }, + "ipv4_router_sc": { + "type": "ip" + }, + "ipv6_extension_headers": { + "type": "long" + }, + "is_multicast": { + "type": "short" + }, + "layer2_frame_delta_count": { + "type": "long" + }, + "layer2_frame_total_count": { + "type": "long" + }, + "layer2_octet_delta_count": { + "type": "long" + }, + "layer2_octet_delta_sum_of_squares": { + "type": "long" + }, + "layer2_octet_total_count": { + "type": "long" + }, + "layer2_octet_total_sum_of_squares": { + "type": "long" + }, + "layer2_segment_id": { + "type": "long" + }, + "layer2packet_section_data": { + "type": "short" + }, + "layer2packet_section_offset": { + "type": "long" + }, + "layer2packet_section_size": { + "type": "long" + }, + "line_card_id": { + "type": "long" + }, + "lower_cli_imit": { + "type": "double" + }, + "max_bieb_ntries": { + "type": "long" + }, + "max_entries_per_user": { + "type": "long" + }, + "max_export_seconds": { + "type": "date" + }, + "max_flow_end_microseconds": { + "type": "date" + }, + "max_flow_end_milliseconds": { + "type": "date" + }, + "max_flow_end_nanoseconds": { + "type": "date" + }, + "max_flow_end_seconds": { + "type": "date" + }, + "max_fragments_pending_reassembly": { + "type": "long" + }, + "max_session_entries": { + "type": "long" + }, + "max_subscribers": { + "type": "long" + }, + "maximum_ip_total_length": { + "type": "long" + }, + "maximum_layer2_total_length": { + "type": "long" + }, + "maximum_ttl": { + "type": "short" + }, + "message_md5_checksum": { + "type": "short" + }, + "message_scope": { + "type": "short" + }, + "metering_process_id": { + "type": "long" + }, + "metro_evc_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "metro_evc_type": { + "type": "short" + }, + "mib_capture_time_semantics": { + "type": "short" + }, + "mib_context_engine_id": { + "type": "short" + }, + "mib_context_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "mib_index_indicator": { + "type": "long" + }, + "mib_module_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "mib_object_description": { + "ignore_above": 1024, + "type": "keyword" + }, + "mib_object_identifier": { + "type": "short" + }, + "mib_object_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "mib_object_syntax": { + "ignore_above": 1024, + "type": "keyword" + }, + "mib_object_value_bits": { + "type": "short" + }, + "mib_object_value_counter": { + "type": "long" + }, + "mib_object_value_gauge": { + "type": "long" + }, + "mib_object_value_integer": { + "type": "long" + }, + "mib_object_value_octet_string": { + "type": "short" + }, + "mib_object_value_oid": { + "type": "short" + }, + "mib_object_value_time_ticks": { + "type": "long" + }, + "mib_object_value_unsigned": { + "type": "long" + }, + "mib_object_valuei_pa_ddress": { + "type": "ip" + }, + "mib_sub_identifier": { + "type": "long" + }, + "min_export_seconds": { + "type": "date" + }, + "min_flow_start_microseconds": { + "type": "date" + }, + "min_flow_start_milliseconds": { + "type": "date" + }, + "min_flow_start_nanoseconds": { + "type": "date" + }, + "min_flow_start_seconds": { + "type": "date" + }, + "minimum_ip_total_length": { + "type": "long" + }, + "minimum_layer2_total_length": { + "type": "long" + }, + "minimum_ttl": { + "type": "short" + }, + "mobile_imsi": { + "ignore_above": 1024, + "type": "keyword" + }, + "mobile_msisdn": { + "ignore_above": 1024, + "type": "keyword" + }, + "monitoring_interval_end_milli_seconds": { + "type": "date" + }, + "monitoring_interval_start_milli_seconds": { + "type": "date" + }, + "mpls_label_stack_depth": { + "type": "long" + }, + "mpls_label_stack_length": { + "type": "long" + }, + "mpls_label_stack_section": { + "type": "short" + }, + "mpls_label_stack_section10": { + "type": "short" + }, + "mpls_label_stack_section2": { + "type": "short" + }, + "mpls_label_stack_section3": { + "type": "short" + }, + "mpls_label_stack_section4": { + "type": "short" + }, + "mpls_label_stack_section5": { + "type": "short" + }, + "mpls_label_stack_section6": { + "type": "short" + }, + "mpls_label_stack_section7": { + "type": "short" + }, + "mpls_label_stack_section8": { + "type": "short" + }, + "mpls_label_stack_section9": { + "type": "short" + }, + "mpls_payload_length": { + "type": "long" + }, + "mpls_payload_packet_section": { + "type": "short" + }, + "mpls_top_label_exp": { + "type": "short" + }, + "mpls_top_label_ipv4_address": { + "type": "ip" + }, + "mpls_top_label_ipv6_address": { + "type": "ip" + }, + "mpls_top_label_prefix_length": { + "type": "short" + }, + "mpls_top_label_stack_section": { + "type": "short" + }, + "mpls_top_label_ttl": { + "type": "short" + }, + "mpls_top_label_type": { + "type": "short" + }, + "mpls_vpn_route_distinguisher": { + "type": "short" + }, + "multicast_replication_factor": { + "type": "long" + }, + "nat_event": { + "type": "short" + }, + "nat_instance_id": { + "type": "long" + }, + "nat_originating_address_realm": { + "type": "short" + }, + "nat_pool_id": { + "type": "long" + }, + "nat_pool_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat_quota_exceeded_event": { + "type": "long" + }, + "nat_threshold_event": { + "type": "long" + }, + "nat_type": { + "type": "short" + }, + "new_connection_delta_count": { + "type": "long" + }, + "next_header_ipv6": { + "type": "short" + }, + "not_sent_flow_total_count": { + "type": "long" + }, + "not_sent_layer2_octet_total_count": { + "type": "long" + }, + "not_sent_octet_total_count": { + "type": "long" + }, + "not_sent_packet_total_count": { + "type": "long" + }, + "observation_domain_id": { + "type": "long" + }, + "observation_domain_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "observation_point_id": { + "type": "long" + }, + "observation_point_type": { + "type": "short" + }, + "observation_time_microseconds": { + "type": "date" + }, + "observation_time_milliseconds": { + "type": "date" + }, + "observation_time_nanoseconds": { + "type": "date" + }, + "observation_time_seconds": { + "type": "date" + }, + "observed_flow_total_count": { + "type": "long" + }, + "octet_delta_count": { + "type": "long" + }, + "octet_delta_sum_of_squares": { + "type": "long" + }, + "octet_total_count": { + "type": "long" + }, + "octet_total_sum_of_squares": { + "type": "long" + }, + "opaque_octets": { + "type": "short" + }, + "original_exporter_ipv4_address": { + "type": "ip" + }, + "original_exporter_ipv6_address": { + "type": "ip" + }, + "original_flows_completed": { + "type": "long" + }, + "original_flows_initiated": { + "type": "long" + }, + "original_flows_present": { + "type": "long" + }, + "original_observation_domain_id": { + "type": "long" + }, + "p2p_technology": { + "ignore_above": 1024, + "type": "keyword" + }, + "packet_delta_count": { + "type": "long" + }, + "packet_total_count": { + "type": "long" + }, + "padding_octets": { + "type": "short" + }, + "payload_length_ipv6": { + "type": "long" + }, + "port_id": { + "type": "long" + }, + "port_range_end": { + "type": "long" + }, + "port_range_num_ports": { + "type": "long" + }, + "port_range_start": { + "type": "long" + }, + "port_range_step_size": { + "type": "long" + }, + "post_destination_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "post_dot1q_customer_vlan_id": { + "type": "long" + }, + "post_dot1q_vlan_id": { + "type": "long" + }, + "post_ip_class_of_service": { + "type": "short" + }, + "post_ip_diff_serv_code_point": { + "type": "short" + }, + "post_ip_precedence": { + "type": "short" + }, + "post_layer2_octet_delta_count": { + "type": "long" + }, + "post_layer2_octet_total_count": { + "type": "long" + }, + "post_mcast_layer2_octet_delta_count": { + "type": "long" + }, + "post_mcast_layer2_octet_total_count": { + "type": "long" + }, + "post_mcast_octet_delta_count": { + "type": "long" + }, + "post_mcast_octet_total_count": { + "type": "long" + }, + "post_mcast_packet_delta_count": { + "type": "long" + }, + "post_mcast_packet_total_count": { + "type": "long" + }, + "post_mpls_top_label_exp": { + "type": "short" + }, + "post_nadt_estination_ipv4_address": { + "type": "ip" + }, + "post_nadt_estination_ipv6_address": { + "type": "ip" + }, + "post_napdt_estination_transport_port": { + "type": "long" + }, + "post_napst_ource_transport_port": { + "type": "long" + }, + "post_nast_ource_ipv4_address": { + "type": "ip" + }, + "post_nast_ource_ipv6_address": { + "type": "ip" + }, + "post_octet_delta_count": { + "type": "long" + }, + "post_octet_total_count": { + "type": "long" + }, + "post_packet_delta_count": { + "type": "long" + }, + "post_packet_total_count": { + "type": "long" + }, + "post_source_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "post_vlan_id": { + "type": "long" + }, + "private_enterprise_number": { + "type": "long" + }, + "protocol_identifier": { + "type": "short" + }, + "pseudo_wire_control_word": { + "type": "long" + }, + "pseudo_wire_destination_ipv4_address": { + "type": "ip" + }, + "pseudo_wire_id": { + "type": "long" + }, + "pseudo_wire_type": { + "type": "long" + }, + "relative_error": { + "type": "double" + }, + "responder_octets": { + "type": "long" + }, + "responder_packets": { + "type": "long" + }, + "rfc3550_jitter_microseconds": { + "type": "long" + }, + "rfc3550_jitter_milliseconds": { + "type": "long" + }, + "rfc3550_jitter_nanoseconds": { + "type": "long" + }, + "rtp_sequence_number": { + "type": "long" + }, + "sampler_id": { + "type": "short" + }, + "sampler_mode": { + "type": "short" + }, + "sampler_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sampler_random_interval": { + "type": "long" + }, + "sampling_algorithm": { + "type": "short" + }, + "sampling_flow_interval": { + "type": "long" + }, + "sampling_flow_spacing": { + "type": "long" + }, + "sampling_interval": { + "type": "long" + }, + "sampling_packet_interval": { + "type": "long" + }, + "sampling_packet_space": { + "type": "long" + }, + "sampling_population": { + "type": "long" + }, + "sampling_probability": { + "type": "double" + }, + "sampling_size": { + "type": "long" + }, + "sampling_time_interval": { + "type": "long" + }, + "sampling_time_space": { + "type": "long" + }, + "section_exported_octets": { + "type": "long" + }, + "section_offset": { + "type": "long" + }, + "selection_sequence_id": { + "type": "long" + }, + "selector_algorithm": { + "type": "long" + }, + "selector_id": { + "type": "long" + }, + "selector_id_total_pkts_observed": { + "type": "long" + }, + "selector_id_total_pkts_selected": { + "type": "long" + }, + "selector_itd_otal_flows_observed": { + "type": "long" + }, + "selector_itd_otal_flows_selected": { + "type": "long" + }, + "selector_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_scope": { + "type": "short" + }, + "source_ipv4_address": { + "type": "ip" + }, + "source_ipv4_prefix": { + "type": "ip" + }, + "source_ipv4_prefix_length": { + "type": "short" + }, + "source_ipv6_address": { + "type": "ip" + }, + "source_ipv6_prefix": { + "type": "ip" + }, + "source_ipv6_prefix_length": { + "type": "short" + }, + "source_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "source_transport_port": { + "type": "long" + }, + "source_transport_ports_limit": { + "type": "long" + }, + "src_traffic_index": { + "type": "long" + }, + "sta_ipv4_address": { + "type": "ip" + }, + "sta_mac_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "system_init_time_milliseconds": { + "type": "date" + }, + "tcp_ack_total_count": { + "type": "long" + }, + "tcp_acknowledgement_number": { + "type": "long" + }, + "tcp_control_bits": { + "type": "long" + }, + "tcp_destination_port": { + "type": "long" + }, + "tcp_fin_total_count": { + "type": "long" + }, + "tcp_header_length": { + "type": "short" + }, + "tcp_options": { + "type": "long" + }, + "tcp_psh_total_count": { + "type": "long" + }, + "tcp_rst_total_count": { + "type": "long" + }, + "tcp_sequence_number": { + "type": "long" + }, + "tcp_source_port": { + "type": "long" + }, + "tcp_syn_total_count": { + "type": "long" + }, + "tcp_urg_total_count": { + "type": "long" + }, + "tcp_urgent_pointer": { + "type": "long" + }, + "tcp_window_scale": { + "type": "long" + }, + "tcp_window_size": { + "type": "long" + }, + "template_id": { + "type": "long" + }, + "total_length_ipv4": { + "type": "long" + }, + "transport_octet_delta_count": { + "type": "long" + }, + "transport_packet_delta_count": { + "type": "long" + }, + "tunnel_technology": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "udp_destination_port": { + "type": "long" + }, + "udp_message_length": { + "type": "long" + }, + "udp_source_port": { + "type": "long" + }, + "upper_cli_imit": { + "type": "double" + }, + "user_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "value_distribution_method": { + "type": "short" + }, + "virtual_station_interface_id": { + "type": "short" + }, + "virtual_station_interface_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_station_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "virtual_station_uuid": { + "type": "short" + }, + "vlan_id": { + "type": "long" + }, + "vpn_identifier": { + "type": "short" + }, + "vr_fname": { + "ignore_above": 1024, + "type": "keyword" + }, + "wlan_channel_id": { + "type": "short" + }, + "wlan_ssid": { + "ignore_above": 1024, + "type": "keyword" + }, + "wtp_mac_address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nginx": { + "properties": { + "access": { + "properties": { + "geoip": { + "type": "object" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "properties": { + "connection_id": { + "type": "long" + } + } + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "osquery": { + "properties": { + "result": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "calendar_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "host_identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "unix_time": { + "type": "long" + } + } + } + } + }, + "postgresql": { + "properties": { + "log": { + "properties": { + "core_id": { + "type": "long" + }, + "database": { + "ignore_above": 1024, + "type": "keyword" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "program": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "redis": { + "properties": { + "log": { + "properties": { + "role": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "santa": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "decision": { + "ignore_above": 1024, + "type": "keyword" + }, + "disk": { + "properties": { + "bsdname": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "mount": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial": { + "ignore_above": 1024, + "type": "keyword" + }, + "volume": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stream": { + "ignore_above": 1024, + "type": "keyword" + }, + "suricata": { + "properties": { + "eve": { + "properties": { + "alert": { + "properties": { + "action": { + "path": "event.outcome", + "type": "alias" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "type": "long" + }, + "rev": { + "type": "long" + }, + "severity": { + "path": "event.severity", + "type": "alias" + }, + "signature": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_id": { + "type": "long" + } + } + }, + "app_proto": { + "path": "network.protocol", + "type": "alias" + }, + "app_proto_expected": { + "ignore_above": 1024, + "type": "keyword" + }, + "app_proto_orig": { + "ignore_above": 1024, + "type": "keyword" + }, + "app_proto_tc": { + "ignore_above": 1024, + "type": "keyword" + }, + "app_proto_ts": { + "ignore_above": 1024, + "type": "keyword" + }, + "dest_ip": { + "path": "destination.ip", + "type": "alias" + }, + "dest_port": { + "path": "destination.port", + "type": "alias" + }, + "dns": { + "properties": { + "id": { + "type": "long" + }, + "rcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "rdata": { + "ignore_above": 1024, + "type": "keyword" + }, + "rrname": { + "ignore_above": 1024, + "type": "keyword" + }, + "rrtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "tx_id": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "fileinfo": { + "properties": { + "filename": { + "path": "file.path", + "type": "alias" + }, + "gaps": { + "type": "boolean" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "path": "file.size", + "type": "alias" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "stored": { + "type": "boolean" + }, + "tx_id": { + "type": "long" + } + } + }, + "flags": { + "type": "object" + }, + "flow": { + "properties": { + "age": { + "type": "long" + }, + "alerted": { + "type": "boolean" + }, + "bytes_toclient": { + "path": "destination.bytes", + "type": "alias" + }, + "bytes_toserver": { + "path": "source.bytes", + "type": "alias" + }, + "end": { + "type": "date" + }, + "pkts_toclient": { + "path": "destination.packets", + "type": "alias" + }, + "pkts_toserver": { + "path": "source.packets", + "type": "alias" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "path": "event.start", + "type": "alias" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flow_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "http": { + "properties": { + "hostname": { + "path": "url.domain", + "type": "alias" + }, + "http_content_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_method": { + "path": "http.request.method", + "type": "alias" + }, + "http_refer": { + "path": "http.request.referrer", + "type": "alias" + }, + "http_user_agent": { + "path": "user_agent.original", + "type": "alias" + }, + "length": { + "path": "http.response.body.bytes", + "type": "alias" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "redirect": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "path": "http.response.status_code", + "type": "alias" + }, + "url": { + "path": "url.original", + "type": "alias" + } + } + }, + "icmp_code": { + "type": "long" + }, + "icmp_type": { + "type": "long" + }, + "in_iface": { + "ignore_above": 1024, + "type": "keyword" + }, + "pcap_cnt": { + "type": "long" + }, + "proto": { + "path": "network.transport", + "type": "alias" + }, + "smtp": { + "properties": { + "helo": { + "ignore_above": 1024, + "type": "keyword" + }, + "mail_from": { + "ignore_above": 1024, + "type": "keyword" + }, + "rcpt_to": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "src_ip": { + "path": "source.ip", + "type": "alias" + }, + "src_port": { + "path": "source.port", + "type": "alias" + }, + "ssh": { + "properties": { + "client": { + "properties": { + "proto_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "software_version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "proto_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "software_version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "app_layer": { + "properties": { + "flow": { + "properties": { + "dcerpc_tcp": { + "type": "long" + }, + "dcerpc_udp": { + "type": "long" + }, + "dns_tcp": { + "type": "long" + }, + "dns_udp": { + "type": "long" + }, + "failed_tcp": { + "type": "long" + }, + "failed_udp": { + "type": "long" + }, + "ftp": { + "type": "long" + }, + "http": { + "type": "long" + }, + "imap": { + "type": "long" + }, + "msn": { + "type": "long" + }, + "smb": { + "type": "long" + }, + "smtp": { + "type": "long" + }, + "ssh": { + "type": "long" + }, + "tls": { + "type": "long" + } + } + }, + "tx": { + "properties": { + "dcerpc_tcp": { + "type": "long" + }, + "dcerpc_udp": { + "type": "long" + }, + "dns_tcp": { + "type": "long" + }, + "dns_udp": { + "type": "long" + }, + "ftp": { + "type": "long" + }, + "http": { + "type": "long" + }, + "smb": { + "type": "long" + }, + "smtp": { + "type": "long" + }, + "ssh": { + "type": "long" + }, + "tls": { + "type": "long" + } + } + } + } + }, + "capture": { + "properties": { + "kernel_drops": { + "type": "long" + }, + "kernel_ifdrops": { + "type": "long" + }, + "kernel_packets": { + "type": "long" + } + } + }, + "decoder": { + "properties": { + "avg_pkt_size": { + "type": "long" + }, + "bytes": { + "type": "long" + }, + "dce": { + "properties": { + "pkt_too_small": { + "type": "long" + } + } + }, + "erspan": { + "type": "long" + }, + "ethernet": { + "type": "long" + }, + "gre": { + "type": "long" + }, + "icmpv4": { + "type": "long" + }, + "icmpv6": { + "type": "long" + }, + "ieee8021ah": { + "type": "long" + }, + "invalid": { + "type": "long" + }, + "ipraw": { + "properties": { + "invalid_ip_version": { + "type": "long" + } + } + }, + "ipv4": { + "type": "long" + }, + "ipv4_in_ipv6": { + "type": "long" + }, + "ipv6": { + "type": "long" + }, + "ipv6_in_ipv6": { + "type": "long" + }, + "ltnull": { + "properties": { + "pkt_too_small": { + "type": "long" + }, + "unsupported_type": { + "type": "long" + } + } + }, + "max_pkt_size": { + "type": "long" + }, + "mpls": { + "type": "long" + }, + "null": { + "type": "long" + }, + "pkts": { + "type": "long" + }, + "ppp": { + "type": "long" + }, + "pppoe": { + "type": "long" + }, + "raw": { + "type": "long" + }, + "sctp": { + "type": "long" + }, + "sll": { + "type": "long" + }, + "tcp": { + "type": "long" + }, + "teredo": { + "type": "long" + }, + "udp": { + "type": "long" + }, + "vlan": { + "type": "long" + }, + "vlan_qinq": { + "type": "long" + } + } + }, + "defrag": { + "properties": { + "ipv4": { + "properties": { + "fragments": { + "type": "long" + }, + "reassembled": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + }, + "ipv6": { + "properties": { + "fragments": { + "type": "long" + }, + "reassembled": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + }, + "max_frag_hits": { + "type": "long" + } + } + }, + "detect": { + "properties": { + "alert": { + "type": "long" + } + } + }, + "dns": { + "properties": { + "memcap_global": { + "type": "long" + }, + "memcap_state": { + "type": "long" + }, + "memuse": { + "type": "long" + } + } + }, + "file_store": { + "properties": { + "open_files": { + "type": "long" + } + } + }, + "flow": { + "properties": { + "emerg_mode_entered": { + "type": "long" + }, + "emerg_mode_over": { + "type": "long" + }, + "icmpv4": { + "type": "long" + }, + "icmpv6": { + "type": "long" + }, + "memcap": { + "type": "long" + }, + "memuse": { + "type": "long" + }, + "spare": { + "type": "long" + }, + "tcp": { + "type": "long" + }, + "tcp_reuse": { + "type": "long" + }, + "udp": { + "type": "long" + } + } + }, + "flow_mgr": { + "properties": { + "bypassed_pruned": { + "type": "long" + }, + "closed_pruned": { + "type": "long" + }, + "est_pruned": { + "type": "long" + }, + "flows_checked": { + "type": "long" + }, + "flows_notimeout": { + "type": "long" + }, + "flows_removed": { + "type": "long" + }, + "flows_timeout": { + "type": "long" + }, + "flows_timeout_inuse": { + "type": "long" + }, + "new_pruned": { + "type": "long" + }, + "rows_busy": { + "type": "long" + }, + "rows_checked": { + "type": "long" + }, + "rows_empty": { + "type": "long" + }, + "rows_maxlen": { + "type": "long" + }, + "rows_skipped": { + "type": "long" + } + } + }, + "http": { + "properties": { + "memcap": { + "type": "long" + }, + "memuse": { + "type": "long" + } + } + }, + "tcp": { + "properties": { + "insert_data_normal_fail": { + "type": "long" + }, + "insert_data_overlap_fail": { + "type": "long" + }, + "insert_list_fail": { + "type": "long" + }, + "invalid_checksum": { + "type": "long" + }, + "memuse": { + "type": "long" + }, + "no_flow": { + "type": "long" + }, + "overlap": { + "type": "long" + }, + "overlap_diff_data": { + "type": "long" + }, + "pseudo": { + "type": "long" + }, + "pseudo_failed": { + "type": "long" + }, + "reassembly_gap": { + "type": "long" + }, + "reassembly_memuse": { + "type": "long" + }, + "rst": { + "type": "long" + }, + "segment_memcap_drop": { + "type": "long" + }, + "sessions": { + "type": "long" + }, + "ssn_memcap_drop": { + "type": "long" + }, + "stream_depth_reached": { + "type": "long" + }, + "syn": { + "type": "long" + }, + "synack": { + "type": "long" + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "tcp": { + "properties": { + "ack": { + "type": "boolean" + }, + "fin": { + "type": "boolean" + }, + "psh": { + "type": "boolean" + }, + "rst": { + "type": "boolean" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "syn": { + "type": "boolean" + }, + "tcp_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcp_flags_tc": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcp_flags_ts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + }, + "tls": { + "properties": { + "fingerprint": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuerdn": { + "ignore_above": 1024, + "type": "keyword" + }, + "notafter": { + "type": "date" + }, + "notbefore": { + "type": "date" + }, + "serial": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_resumed": { + "type": "boolean" + }, + "sni": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tx_id": { + "type": "long" + } + } + } + } + }, + "syslog": { + "properties": { + "facility": { + "type": "long" + }, + "facility_label": { + "ignore_above": 1024, + "type": "keyword" + }, + "priority": { + "type": "long" + }, + "severity_label": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "auth": { + "properties": { + "groupadd": { + "type": "object" + }, + "ssh": { + "properties": { + "dropped_ip": { + "type": "ip" + }, + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "sudo": { + "properties": { + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "error": { + "ignore_above": 1024, + "type": "keyword" + }, + "pwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "useradd": { + "properties": { + "home": { + "ignore_above": 1024, + "type": "keyword" + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "syslog": { + "type": "object" + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "traefik": { + "properties": { + "access": { + "properties": { + "backend_url": { + "ignore_above": 1024, + "type": "keyword" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "properties": { + "city_name": { + "path": "source.geo.city_name", + "type": "alias" + }, + "continent_name": { + "path": "source.geo.continent_name", + "type": "alias" + }, + "country_iso_code": { + "path": "source.geo.country_iso_code", + "type": "alias" + }, + "location": { + "path": "source.geo.location", + "type": "alias" + }, + "region_iso_code": { + "path": "source.geo.region_iso_code", + "type": "alias" + }, + "region_name": { + "path": "source.geo.region_name", + "type": "alias" + } + } + }, + "request_count": { + "type": "long" + }, + "user_agent": { + "properties": { + "device": { + "path": "user_agent.device.name", + "type": "alias" + }, + "name": { + "path": "user_agent.name", + "type": "alias" + }, + "original": { + "path": "user_agent.original", + "type": "alias" + }, + "os": { + "path": "user_agent.os.full_name", + "type": "alias" + }, + "os_name": { + "path": "user_agent.os.name", + "type": "alias" + } + } + }, + "user_identifier": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "zeek": { + "properties": { + "connection": { + "properties": { + "history": { + "ignore_above": 1024, + "type": "keyword" + }, + "inner_vlan": { + "type": "long" + }, + "local_orig": { + "type": "boolean" + }, + "local_resp": { + "type": "boolean" + }, + "missed_bytes": { + "type": "long" + }, + "orig_l2_addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "resp_l2_addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "type": "long" + } + } + }, + "dns": { + "properties": { + "AA": { + "type": "boolean" + }, + "RA": { + "type": "boolean" + }, + "RD": { + "type": "boolean" + }, + "TC": { + "type": "boolean" + }, + "TTLs": { + "type": "double" + }, + "answers": { + "ignore_above": 1024, + "type": "keyword" + }, + "qclass": { + "type": "long" + }, + "qclass_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "qtype": { + "type": "long" + }, + "qtype_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "rcode": { + "type": "long" + }, + "rcode_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "rejected": { + "type": "boolean" + }, + "rtt": { + "type": "double" + }, + "saw_query": { + "type": "boolean" + }, + "saw_reply": { + "type": "boolean" + }, + "total_answers": { + "type": "long" + }, + "total_replies": { + "type": "long" + }, + "trans_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "files": { + "properties": { + "analyzers": { + "ignore_above": 1024, + "type": "keyword" + }, + "depth": { + "type": "long" + }, + "duration": { + "type": "double" + }, + "entropy": { + "type": "double" + }, + "extracted": { + "ignore_above": 1024, + "type": "keyword" + }, + "extracted_cutoff": { + "type": "boolean" + }, + "extracted_size": { + "type": "long" + }, + "filename": { + "ignore_above": 1024, + "type": "keyword" + }, + "fuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_orig": { + "type": "boolean" + }, + "local_orig": { + "type": "boolean" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "missing_bytes": { + "type": "long" + }, + "overflow_bytes": { + "type": "long" + }, + "parent_fuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "rx_host": { + "type": "ip" + }, + "seen_bytes": { + "type": "long" + }, + "session_ids": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "source": { + "ignore_above": 1024, + "type": "keyword" + }, + "timedout": { + "type": "boolean" + }, + "total_bytes": { + "type": "long" + }, + "tx_host": { + "type": "ip" + } + } + }, + "fnotice": { + "properties": { + "file": { + "properties": { + "total_bytes": { + "type": "long" + } + } + } + } + }, + "http": { + "properties": { + "captured_password": { + "type": "boolean" + }, + "client_header_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "info_code": { + "type": "long" + }, + "info_msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "orig_filenames": { + "ignore_above": 1024, + "type": "keyword" + }, + "orig_fuids": { + "ignore_above": 1024, + "type": "keyword" + }, + "orig_mime_depth": { + "type": "long" + }, + "orig_mime_types": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "proxied": { + "ignore_above": 1024, + "type": "keyword" + }, + "range_request": { + "type": "boolean" + }, + "resp_filenames": { + "ignore_above": 1024, + "type": "keyword" + }, + "resp_fuids": { + "ignore_above": 1024, + "type": "keyword" + }, + "resp_mime_depth": { + "type": "long" + }, + "resp_mime_types": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_header_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "trans_depth": { + "type": "long" + } + } + }, + "notice": { + "properties": { + "actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "connection_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "dropped": { + "type": "boolean" + }, + "email_body_sections": { + "norms": false, + "type": "text" + }, + "email_delay_tokens": { + "ignore_above": 1024, + "type": "keyword" + }, + "file": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "is_orig": { + "type": "boolean" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "missing_bytes": { + "type": "long" + }, + "overflow_bytes": { + "type": "long" + }, + "parent_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "seen_bytes": { + "type": "long" + }, + "source": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "n": { + "type": "long" + }, + "note": { + "ignore_above": 1024, + "type": "keyword" + }, + "peer_descr": { + "norms": false, + "type": "text" + }, + "peer_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sub": { + "ignore_above": 1024, + "type": "keyword" + }, + "suppress_for": { + "type": "double" + } + } + }, + "session_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ssl": { + "properties": { + "cert_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "cert_chain_fuids": { + "ignore_above": 1024, + "type": "keyword" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client_cert_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "client_cert_chain_fuids": { + "ignore_above": 1024, + "type": "keyword" + }, + "client_issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "client_subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_alert": { + "ignore_above": 1024, + "type": "keyword" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "validation_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "validation_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "2000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_nginx/data.json.gz b/x-pack/test/functional/es_archives/ml/module_nginx/data.json.gz new file mode 100644 index 0000000000000..8ed959d02001d Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_nginx/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_nginx/mappings.json b/x-pack/test/functional/es_archives/ml/module_nginx/mappings.json new file mode 100644 index 0000000000000..7a6623f40f39e --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_nginx/mappings.json @@ -0,0 +1,2675 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_nginx", + "mappings": { + "_meta": { + "beat": "filebeat", + "version": "7.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kibana.log.meta": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "kibana.log.meta.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "apache": { + "properties": { + "access": { + "properties": { + "ssl": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "apache2": { + "properties": { + "access": { + "properties": { + "geoip": { + "type": "object" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "type": "object" + } + } + }, + "auditd": { + "properties": { + "log": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "type": "ip" + }, + "geoip": { + "type": "object" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "type": "ip" + }, + "lport": { + "type": "long" + }, + "new_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "type": "long" + }, + "sequence": { + "type": "long" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "certificate": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "audit": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "indices": { + "ignore_above": 1024, + "type": "keyword" + }, + "layer": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "request": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "params": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "realm": { + "ignore_above": 1024, + "type": "keyword" + }, + "roles": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cluster": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "deprecation": { + "type": "object" + }, + "gc": { + "properties": { + "heap": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "jvm_runtime_sec": { + "type": "float" + }, + "old_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + }, + "phase": { + "properties": { + "class_unload_time_sec": { + "type": "float" + }, + "cpu_time": { + "properties": { + "real_sec": { + "type": "float" + }, + "sys_sec": { + "type": "float" + }, + "user_sec": { + "type": "float" + } + } + }, + "duration_sec": { + "type": "float" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parallel_rescan_time_sec": { + "type": "float" + }, + "scrub_string_table_time_sec": { + "type": "float" + }, + "scrub_symbol_table_time_sec": { + "type": "float" + }, + "weak_refs_processing_time_sec": { + "type": "float" + } + } + }, + "stopping_threads_time_sec": { + "type": "float" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threads_total_stop_time_sec": { + "type": "float" + }, + "young_gen": { + "properties": { + "size_kb": { + "type": "long" + }, + "used_kb": { + "type": "long" + } + } + } + } + }, + "index": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "gc": { + "properties": { + "collection_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "observation_duration": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "overhead_seq": { + "type": "long" + }, + "young": { + "properties": { + "one": { + "type": "long" + }, + "two": { + "type": "long" + } + } + } + } + } + } + }, + "shard": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "extra_source": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "routing": { + "ignore_above": 1024, + "type": "keyword" + }, + "search_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "source_query": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "ignore_above": 1024, + "type": "keyword" + }, + "took": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_hits": { + "ignore_above": 1024, + "type": "keyword" + }, + "total_shards": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "types": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fileset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "haproxy": { + "properties": { + "backend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "backend_queue": { + "type": "long" + }, + "bind_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes_read": { + "type": "long" + }, + "client": { + "type": "object" + }, + "connection_wait_time_ms": { + "type": "long" + }, + "connections": { + "properties": { + "active": { + "type": "long" + }, + "backend": { + "type": "long" + }, + "frontend": { + "type": "long" + }, + "retries": { + "type": "long" + }, + "server": { + "type": "long" + } + } + }, + "destination": { + "type": "object" + }, + "error_message": { + "norms": false, + "type": "text" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "http": { + "properties": { + "request": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + }, + "raw_request_line": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_wait_ms": { + "type": "long" + }, + "time_wait_without_data_ms": { + "type": "long" + } + } + }, + "response": { + "properties": { + "captured_cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "captured_headers": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_queue": { + "type": "long" + }, + "source": { + "norms": false, + "type": "text" + }, + "tcp": { + "properties": { + "connection_waiting_time_ms": { + "type": "long" + } + } + }, + "termination_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_backend_connect": { + "type": "long" + }, + "time_queue": { + "type": "long" + }, + "total_waiting_time_ms": { + "type": "long" + } + } + }, + "hash": { + "properties": { + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "icinga": { + "properties": { + "debug": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "main": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "startup": { + "properties": { + "facility": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "iis": { + "properties": { + "access": { + "properties": { + "cookie": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "site_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "sub_status": { + "type": "long" + }, + "user_agent": { + "type": "object" + }, + "win32_status": { + "type": "long" + } + } + }, + "error": { + "properties": { + "geoip": { + "type": "object" + }, + "queue_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason_phrase": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "input": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kafka": { + "properties": { + "log": { + "properties": { + "class": { + "norms": false, + "type": "text" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "trace": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "norms": false, + "type": "text" + }, + "message": { + "norms": false, + "type": "text" + } + } + } + } + } + } + }, + "kibana": { + "properties": { + "log": { + "properties": { + "meta": { + "type": "object" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "type": "object" + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "file": { + "properties": { + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "type": "long" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "logstash": { + "properties": { + "log": { + "properties": { + "log_event": { + "type": "object" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "event": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "plugin_params_object": { + "type": "object" + }, + "plugin_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "thread": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "took_in_millis": { + "type": "long" + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "mongodb": { + "properties": { + "log": { + "properties": { + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "context": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mysql": { + "properties": { + "error": { + "type": "object" + }, + "slowlog": { + "properties": { + "bytes_sent": { + "type": "long" + }, + "current_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesort": { + "type": "boolean" + }, + "filesort_on_disk": { + "type": "boolean" + }, + "full_join": { + "type": "boolean" + }, + "full_scan": { + "type": "boolean" + }, + "innodb": { + "properties": { + "io_r_bytes": { + "type": "long" + }, + "io_r_ops": { + "type": "long" + }, + "io_r_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "pages_distinct": { + "type": "long" + }, + "queue_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "rec_lock_wait": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "trx_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "killed": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_errno": { + "ignore_above": 1024, + "type": "keyword" + }, + "lock_time": { + "properties": { + "sec": { + "type": "float" + } + } + }, + "log_slow_rate_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "log_slow_rate_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "merge_passes": { + "type": "long" + }, + "priority_queue": { + "type": "boolean" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "query_cache_hit": { + "type": "boolean" + }, + "rows_affected": { + "type": "long" + }, + "rows_examined": { + "type": "long" + }, + "rows_sent": { + "type": "long" + }, + "schema": { + "ignore_above": 1024, + "type": "keyword" + }, + "tmp_disk_tables": { + "type": "long" + }, + "tmp_table": { + "type": "boolean" + }, + "tmp_table_on_disk": { + "type": "boolean" + }, + "tmp_table_sizes": { + "type": "long" + }, + "tmp_tables": { + "type": "long" + } + } + }, + "thread_id": { + "type": "long" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nginx": { + "properties": { + "access": { + "properties": { + "agent": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "type": "object" + }, + "remote_ip_list": { + "ignore_above": 1024, + "type": "keyword" + }, + "user_agent": { + "type": "object" + } + } + }, + "error": { + "properties": { + "connection_id": { + "type": "long" + } + } + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "osquery": { + "properties": { + "result": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "calendar_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "host_identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "unix_time": { + "type": "long" + } + } + } + } + }, + "postgresql": { + "properties": { + "log": { + "properties": { + "core_id": { + "type": "long" + }, + "database": { + "ignore_above": 1024, + "type": "keyword" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "program": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "redis": { + "properties": { + "log": { + "properties": { + "role": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "slowlog": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "santa": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "decision": { + "ignore_above": 1024, + "type": "keyword" + }, + "disk": { + "properties": { + "bsdname": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "mount": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial": { + "ignore_above": 1024, + "type": "keyword" + }, + "volume": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + } + } + }, + "stream": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "type": "long" + }, + "facility_label": { + "ignore_above": 1024, + "type": "keyword" + }, + "priority": { + "type": "long" + }, + "severity_label": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "auth": { + "properties": { + "groupadd": { + "type": "object" + }, + "ssh": { + "properties": { + "dropped_ip": { + "type": "ip" + }, + "geoip": { + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "sudo": { + "properties": { + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "error": { + "ignore_above": 1024, + "type": "keyword" + }, + "pwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "useradd": { + "properties": { + "home": { + "ignore_above": 1024, + "type": "keyword" + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "syslog": { + "type": "object" + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "traefik": { + "properties": { + "access": { + "properties": { + "backend_url": { + "ignore_above": 1024, + "type": "keyword" + }, + "frontend_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "geoip": { + "properties": { + "city_name": { + "path": "source.geo.city_name", + "type": "alias" + }, + "continent_name": { + "path": "source.geo.continent_name", + "type": "alias" + }, + "country_iso_code": { + "path": "source.geo.country_iso_code", + "type": "alias" + }, + "location": { + "path": "source.geo.location", + "type": "alias" + }, + "region_iso_code": { + "path": "source.geo.region_iso_code", + "type": "alias" + }, + "region_name": { + "path": "source.geo.region_name", + "type": "alias" + } + } + }, + "request_count": { + "type": "long" + }, + "user_agent": { + "properties": { + "device": { + "path": "user_agent.device.name", + "type": "alias" + }, + "major": { + "path": "user_agent.major", + "type": "alias" + }, + "minor": { + "path": "user_agent.minor", + "type": "alias" + }, + "name": { + "path": "user_agent.name", + "type": "alias" + }, + "original": { + "path": "user_agent.original", + "type": "alias" + }, + "os": { + "path": "user_agent.os.full_name", + "type": "alias" + }, + "os_major": { + "path": "user_agent.os.major", + "type": "alias" + }, + "os_minor": { + "path": "user_agent.os.minor", + "type": "alias" + }, + "os_name": { + "path": "user_agent.os.name", + "type": "alias" + }, + "patch": { + "path": "user_agent.patch", + "type": "alias" + } + } + }, + "user_identifier": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "patch": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/data.json.gz b/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/data.json.gz new file mode 100644 index 0000000000000..76357734a94aa Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/mappings.json b/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/mappings.json new file mode 100644 index 0000000000000..2b22f5eb431d4 --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_sample_ecommerce/mappings.json @@ -0,0 +1,211 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_sample_ecommerce", + "mappings": { + "properties": { + "category": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "currency": { + "type": "keyword" + }, + "customer_birth_date": { + "type": "date" + }, + "customer_first_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_full_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_gender": { + "type": "keyword" + }, + "customer_id": { + "type": "keyword" + }, + "customer_last_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_phone": { + "type": "keyword" + }, + "day_of_week": { + "type": "keyword" + }, + "day_of_week_i": { + "type": "integer" + }, + "email": { + "type": "keyword" + }, + "geoip": { + "properties": { + "city_name": { + "type": "keyword" + }, + "continent_name": { + "type": "keyword" + }, + "country_iso_code": { + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_name": { + "type": "keyword" + } + } + }, + "manufacturer": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "order_date": { + "type": "date" + }, + "order_id": { + "type": "keyword" + }, + "products": { + "properties": { + "_id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "base_price": { + "type": "half_float" + }, + "base_unit_price": { + "type": "half_float" + }, + "category": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "created_on": { + "type": "date" + }, + "discount_amount": { + "type": "half_float" + }, + "discount_percentage": { + "type": "half_float" + }, + "manufacturer": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "min_price": { + "type": "half_float" + }, + "price": { + "type": "half_float" + }, + "product_id": { + "type": "long" + }, + "product_name": { + "analyzer": "english", + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "quantity": { + "type": "integer" + }, + "sku": { + "type": "keyword" + }, + "tax_amount": { + "type": "half_float" + }, + "taxful_price": { + "type": "half_float" + }, + "taxless_price": { + "type": "half_float" + }, + "unit_discount_amount": { + "type": "half_float" + } + } + }, + "sku": { + "type": "keyword" + }, + "taxful_total_price": { + "type": "half_float" + }, + "taxless_total_price": { + "type": "half_float" + }, + "total_quantity": { + "type": "integer" + }, + "total_unique_products": { + "type": "integer" + }, + "type": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_sample_logs/data.json.gz b/x-pack/test/functional/es_archives/ml/module_sample_logs/data.json.gz new file mode 100644 index 0000000000000..b49b826f066d0 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_sample_logs/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/sample_logs/mappings.json b/x-pack/test/functional/es_archives/ml/module_sample_logs/mappings.json similarity index 96% rename from x-pack/test/functional/es_archives/ml/sample_logs/mappings.json rename to x-pack/test/functional/es_archives/ml/module_sample_logs/mappings.json index 62f69063695e7..88ff2ed01a624 100644 --- a/x-pack/test/functional/es_archives/ml/sample_logs/mappings.json +++ b/x-pack/test/functional/es_archives/ml/module_sample_logs/mappings.json @@ -3,7 +3,7 @@ "value": { "aliases": { }, - "index": "kibana_sample_data_logs", + "index": "ft_module_sample_logs", "mappings": { "properties": { "@timestamp": { @@ -158,10 +158,9 @@ }, "settings": { "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", + "number_of_replicas": "1", "number_of_shards": "1" } } } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/data.json.gz b/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/data.json.gz new file mode 100644 index 0000000000000..64f582011db50 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/mappings.json b/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/mappings.json new file mode 100644 index 0000000000000..818036fa32ebf --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_siem_auditbeat/mappings.json @@ -0,0 +1,1663 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_siem_auditbeat", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "auditd": { + "properties": { + "data": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "a1": { + "ignore_above": 1024, + "type": "keyword" + }, + "a2": { + "ignore_above": 1024, + "type": "keyword" + }, + "a3": { + "ignore_above": 1024, + "type": "keyword" + }, + "a[0-3]": { + "ignore_above": 1024, + "type": "keyword" + }, + "acct": { + "ignore_above": 1024, + "type": "keyword" + }, + "acl": { + "ignore_above": 1024, + "type": "keyword" + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "added": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "apparmor": { + "ignore_above": 1024, + "type": "keyword" + }, + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "argc": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_wait_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_failure": { + "ignore_above": 1024, + "type": "keyword" + }, + "banners": { + "ignore_above": 1024, + "type": "keyword" + }, + "bool": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "capability": { + "ignore_above": 1024, + "type": "keyword" + }, + "cgroup": { + "ignore_above": 1024, + "type": "keyword" + }, + "changed": { + "ignore_above": 1024, + "type": "keyword" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "compat": { + "ignore_above": 1024, + "type": "keyword" + }, + "daddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "default-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "dmac": { + "ignore_above": 1024, + "type": "keyword" + }, + "dport": { + "ignore_above": 1024, + "type": "keyword" + }, + "enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "entries": { + "ignore_above": 1024, + "type": "keyword" + }, + "exit": { + "ignore_above": 1024, + "type": "keyword" + }, + "fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "fd": { + "ignore_above": 1024, + "type": "keyword" + }, + "fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "feature": { + "ignore_above": 1024, + "type": "keyword" + }, + "fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "file": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "format": { + "ignore_above": 1024, + "type": "keyword" + }, + "fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "grantors": { + "ignore_above": 1024, + "type": "keyword" + }, + "grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "hook": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "igid": { + "ignore_above": 1024, + "type": "keyword" + }, + "img-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "inif": { + "ignore_above": 1024, + "type": "keyword" + }, + "ino": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "invalid_context": { + "ignore_above": 1024, + "type": "keyword" + }, + "ioctlcmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipx-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "iuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "ksize": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "len": { + "ignore_above": 1024, + "type": "keyword" + }, + "list": { + "ignore_above": 1024, + "type": "keyword" + }, + "lport": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "macproto": { + "ignore_above": 1024, + "type": "keyword" + }, + "maj": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "nargs": { + "ignore_above": 1024, + "type": "keyword" + }, + "net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ocomm": { + "ignore_above": 1024, + "type": "keyword" + }, + "oflag": { + "ignore_above": 1024, + "type": "keyword" + }, + "old": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_val": { + "ignore_above": 1024, + "type": "keyword" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "opid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oses": { + "ignore_above": 1024, + "type": "keyword" + }, + "outif": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + }, + "per": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm_mask": { + "ignore_above": 1024, + "type": "keyword" + }, + "permissive": { + "ignore_above": 1024, + "type": "keyword" + }, + "pfs": { + "ignore_above": 1024, + "type": "keyword" + }, + "printer": { + "ignore_above": 1024, + "type": "keyword" + }, + "prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "proto": { + "ignore_above": 1024, + "type": "keyword" + }, + "qbytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "range": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "removed": { + "ignore_above": 1024, + "type": "keyword" + }, + "res": { + "ignore_above": 1024, + "type": "keyword" + }, + "resrc": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "ignore_above": 1024, + "type": "keyword" + }, + "sauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "scontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "selected-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperm": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperms": { + "ignore_above": 1024, + "type": "keyword" + }, + "seqno": { + "ignore_above": 1024, + "type": "keyword" + }, + "seresult": { + "ignore_above": 1024, + "type": "keyword" + }, + "ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "sig": { + "ignore_above": 1024, + "type": "keyword" + }, + "sigev_signo": { + "ignore_above": 1024, + "type": "keyword" + }, + "smac": { + "ignore_above": 1024, + "type": "keyword" + }, + "socket": { + "properties": { + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "ignore_above": 1024, + "type": "keyword" + }, + "saddr": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "spid": { + "ignore_above": 1024, + "type": "keyword" + }, + "sport": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "subj": { + "ignore_above": 1024, + "type": "keyword" + }, + "success": { + "ignore_above": 1024, + "type": "keyword" + }, + "syscall": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "tclass": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "uri": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "val": { + "ignore_above": 1024, + "type": "keyword" + }, + "ver": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "watch": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "paths": { + "properties": { + "dev": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "nametype": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_role": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "objtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "ogid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ouid": { + "ignore_above": 1024, + "type": "keyword" + }, + "rdev": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "summary": { + "properties": { + "actor": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "how": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "name_map": { + "type": "object" + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selinux": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/data.json.gz b/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/data.json.gz new file mode 100644 index 0000000000000..d8e3af5eeb48d Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/mappings.json b/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/mappings.json new file mode 100644 index 0000000000000..9c0ca8901a0da --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_siem_packetbeat/mappings.json @@ -0,0 +1,4643 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_siem_packetbeat", + "mappings": { + "_meta": { + "beat": "packetbeat", + "version": "7.6.2" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "dns.answers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "dns.answers.*" + } + }, + { + "log.syslog": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "log.syslog.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "amqp.headers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "amqp.headers.*" + } + }, + { + "cassandra.response.supported": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "cassandra.response.supported.*" + } + }, + { + "http.request.headers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "http.request.headers.*" + } + }, + { + "http.response.headers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "http.response.headers.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "amqp": { + "properties": { + "app-id": { + "ignore_above": 1024, + "type": "keyword" + }, + "arguments": { + "type": "object" + }, + "auto-delete": { + "type": "boolean" + }, + "class-id": { + "type": "long" + }, + "consumer-count": { + "type": "long" + }, + "consumer-tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "content-encoding": { + "ignore_above": 1024, + "type": "keyword" + }, + "content-type": { + "ignore_above": 1024, + "type": "keyword" + }, + "correlation-id": { + "ignore_above": 1024, + "type": "keyword" + }, + "delivery-mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "delivery-tag": { + "type": "long" + }, + "durable": { + "type": "boolean" + }, + "exchange": { + "ignore_above": 1024, + "type": "keyword" + }, + "exchange-type": { + "ignore_above": 1024, + "type": "keyword" + }, + "exclusive": { + "type": "boolean" + }, + "expiration": { + "ignore_above": 1024, + "type": "keyword" + }, + "headers": { + "type": "object" + }, + "if-empty": { + "type": "boolean" + }, + "if-unused": { + "type": "boolean" + }, + "immediate": { + "type": "boolean" + }, + "mandatory": { + "type": "boolean" + }, + "message-count": { + "type": "long" + }, + "message-id": { + "ignore_above": 1024, + "type": "keyword" + }, + "method-id": { + "type": "long" + }, + "multiple": { + "type": "boolean" + }, + "no-ack": { + "type": "boolean" + }, + "no-local": { + "type": "boolean" + }, + "no-wait": { + "type": "boolean" + }, + "passive": { + "type": "boolean" + }, + "priority": { + "type": "long" + }, + "queue": { + "ignore_above": 1024, + "type": "keyword" + }, + "redelivered": { + "type": "boolean" + }, + "reply-code": { + "type": "long" + }, + "reply-text": { + "ignore_above": 1024, + "type": "keyword" + }, + "reply-to": { + "ignore_above": 1024, + "type": "keyword" + }, + "routing-key": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user-id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes_in": { + "path": "source.bytes", + "type": "alias" + }, + "bytes_out": { + "path": "destination.bytes", + "type": "alias" + }, + "cassandra": { + "properties": { + "no_request": { + "type": "boolean" + }, + "request": { + "properties": { + "headers": { + "properties": { + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "length": { + "type": "long" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "stream": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "authentication": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "type": "long" + }, + "details": { + "properties": { + "alive": { + "type": "long" + }, + "arg_types": { + "ignore_above": 1024, + "type": "keyword" + }, + "blockfor": { + "type": "long" + }, + "data_present": { + "type": "boolean" + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "num_failures": { + "ignore_above": 1024, + "type": "keyword" + }, + "read_consistency": { + "ignore_above": 1024, + "type": "keyword" + }, + "received": { + "type": "long" + }, + "required": { + "type": "long" + }, + "stmt_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "write_type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "change": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "schema_change": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "change": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "headers": { + "properties": { + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "length": { + "type": "long" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "stream": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "result": { + "properties": { + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "prepared": { + "properties": { + "prepared_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "req_meta": { + "properties": { + "col_count": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "paging_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "pkey_columns": { + "type": "long" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resp_meta": { + "properties": { + "col_count": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "paging_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "pkey_columns": { + "type": "long" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "rows": { + "properties": { + "meta": { + "properties": { + "col_count": { + "type": "long" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "paging_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "pkey_columns": { + "type": "long" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "num_rows": { + "type": "long" + } + } + }, + "schema_change": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "change": { + "ignore_above": 1024, + "type": "keyword" + }, + "keyspace": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "supported": { + "type": "object" + }, + "warnings": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dhcpv4": { + "properties": { + "assigned_ip": { + "type": "ip" + }, + "client_ip": { + "type": "ip" + }, + "client_mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "hardware_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "hops": { + "type": "long" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "option": { + "properties": { + "boot_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "broadcast_address": { + "type": "ip" + }, + "class_identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "dns_servers": { + "type": "ip" + }, + "domain_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip_address_lease_time_sec": { + "type": "long" + }, + "max_dhcp_message_size": { + "type": "long" + }, + "message": { + "norms": false, + "type": "text" + }, + "message_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "ntp_servers": { + "type": "ip" + }, + "parameter_request_list": { + "ignore_above": 1024, + "type": "keyword" + }, + "rebinding_time_sec": { + "type": "long" + }, + "renewal_time_sec": { + "type": "long" + }, + "requested_ip_address": { + "type": "ip" + }, + "router": { + "type": "ip" + }, + "server_identifier": { + "type": "ip" + }, + "subnet_mask": { + "type": "ip" + }, + "time_servers": { + "type": "ip" + }, + "utc_time_offset_sec": { + "type": "long" + }, + "vendor_identifying_options": { + "type": "object" + } + } + }, + "relay_ip": { + "type": "ip" + }, + "seconds": { + "type": "long" + }, + "server_ip": { + "type": "ip" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "transaction_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dns": { + "properties": { + "additionals": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "additionals_count": { + "type": "long" + }, + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "answers_count": { + "type": "long" + }, + "authorities": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "authorities_count": { + "type": "long" + }, + "flags": { + "properties": { + "authentic_data": { + "type": "boolean" + }, + "authoritative": { + "type": "boolean" + }, + "checking_disabled": { + "type": "boolean" + }, + "recursion_available": { + "type": "boolean" + }, + "recursion_desired": { + "type": "boolean" + }, + "truncated_response": { + "type": "boolean" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "opt": { + "properties": { + "do": { + "type": "boolean" + }, + "ext_rcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "udp_size": { + "type": "long" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "etld_plus_one": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flow": { + "properties": { + "final": { + "type": "boolean" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "vlan": { + "type": "long" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "properties": { + "content-length": { + "type": "long" + } + } + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "properties": { + "content-length": { + "type": "long" + }, + "content-type": { + "type": "keyword" + } + } + }, + "status_code": { + "type": "long" + }, + "status_phrase": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "icmp": { + "properties": { + "request": { + "properties": { + "code": { + "type": "long" + }, + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "type": "long" + } + } + }, + "response": { + "properties": { + "code": { + "type": "long" + }, + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jolokia": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secured": { + "type": "boolean" + }, + "server": { + "properties": { + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "memcache": { + "properties": { + "protocol_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "request": { + "properties": { + "automove": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "cas_unique": { + "type": "long" + }, + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "count_values": { + "type": "long" + }, + "delta": { + "type": "long" + }, + "dest_class": { + "type": "long" + }, + "exptime": { + "type": "long" + }, + "flags": { + "type": "long" + }, + "initial": { + "type": "long" + }, + "line": { + "ignore_above": 1024, + "type": "keyword" + }, + "noreply": { + "type": "boolean" + }, + "opaque": { + "type": "long" + }, + "opcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "opcode_value": { + "type": "long" + }, + "quiet": { + "type": "boolean" + }, + "raw_args": { + "ignore_above": 1024, + "type": "keyword" + }, + "sleep_us": { + "type": "long" + }, + "source_class": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vbucket": { + "type": "long" + }, + "verbosity": { + "type": "long" + } + } + }, + "response": { + "properties": { + "bytes": { + "type": "long" + }, + "cas_unique": { + "type": "long" + }, + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "count_values": { + "type": "long" + }, + "error_msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "type": "long" + }, + "opaque": { + "type": "long" + }, + "opcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "opcode_value": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "mongodb": { + "properties": { + "cursorId": { + "ignore_above": 1024, + "type": "keyword" + }, + "error": { + "ignore_above": 1024, + "type": "keyword" + }, + "fullCollectionName": { + "ignore_above": 1024, + "type": "keyword" + }, + "numberReturned": { + "type": "long" + }, + "numberToReturn": { + "type": "long" + }, + "numberToSkip": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "returnFieldsSelector": { + "ignore_above": 1024, + "type": "keyword" + }, + "selector": { + "ignore_above": 1024, + "type": "keyword" + }, + "startingFrom": { + "ignore_above": 1024, + "type": "keyword" + }, + "update": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mysql": { + "properties": { + "affected_rows": { + "type": "long" + }, + "error_code": { + "type": "long" + }, + "error_message": { + "ignore_above": 1024, + "type": "keyword" + }, + "insert_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "num_fields": { + "ignore_above": 1024, + "type": "keyword" + }, + "num_rows": { + "ignore_above": 1024, + "type": "keyword" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nfs": { + "properties": { + "minor_version": { + "type": "long" + }, + "opcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "notes": { + "path": "error.message", + "type": "alias" + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "params": { + "norms": false, + "type": "text" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "pgsql": { + "properties": { + "error_code": { + "type": "long" + }, + "error_message": { + "ignore_above": 1024, + "type": "keyword" + }, + "error_severity": { + "ignore_above": 1024, + "type": "keyword" + }, + "num_fields": { + "ignore_above": 1024, + "type": "keyword" + }, + "num_rows": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "redis": { + "properties": { + "error": { + "ignore_above": 1024, + "type": "keyword" + }, + "return_value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "request": { + "norms": false, + "type": "text" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "response": { + "norms": false, + "type": "text" + }, + "rpc": { + "properties": { + "auth_flavor": { + "ignore_above": 1024, + "type": "keyword" + }, + "cred": { + "properties": { + "gid": { + "type": "long" + }, + "gids": { + "ignore_above": 1024, + "type": "keyword" + }, + "machinename": { + "ignore_above": 1024, + "type": "keyword" + }, + "stamp": { + "type": "long" + }, + "uid": { + "type": "long" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "xid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "start": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "thrift": { + "properties": { + "exceptions": { + "ignore_above": 1024, + "type": "keyword" + }, + "params": { + "ignore_above": 1024, + "type": "keyword" + }, + "return_value": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tls": { + "properties": { + "alert_types": { + "path": "tls.detailed.alert_types", + "type": "alias" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client_certificate": { + "properties": { + "alternative_names": { + "path": "tls.detailed.client_certificate.alternative_names", + "type": "alias" + }, + "issuer": { + "properties": { + "common_name": { + "path": "tls.detailed.client_certificate.issuer.common_name", + "type": "alias" + }, + "country": { + "path": "tls.detailed.client_certificate.issuer.country", + "type": "alias" + }, + "locality": { + "path": "tls.detailed.client_certificate.issuer.locality", + "type": "alias" + }, + "organization": { + "path": "tls.detailed.client_certificate.issuer.organization", + "type": "alias" + }, + "organizational_unit": { + "path": "tls.detailed.client_certificate.issuer.organizational_unit", + "type": "alias" + }, + "province": { + "path": "tls.detailed.client_certificate.issuer.province", + "type": "alias" + } + } + }, + "not_after": { + "path": "tls.detailed.client_certificate.not_after", + "type": "alias" + }, + "not_before": { + "path": "tls.detailed.client_certificate.not_before", + "type": "alias" + }, + "public_key_algorithm": { + "path": "tls.detailed.client_certificate.public_key_algorithm", + "type": "alias" + }, + "public_key_size": { + "path": "tls.detailed.client_certificate.public_key_size", + "type": "alias" + }, + "serial_number": { + "path": "tls.detailed.client_certificate.serial_number", + "type": "alias" + }, + "signature_algorithm": { + "path": "tls.detailed.client_certificate.signature_algorithm", + "type": "alias" + }, + "subject": { + "properties": { + "common_name": { + "path": "tls.detailed.client_certificate.subject.common_name", + "type": "alias" + }, + "country": { + "path": "tls.detailed.client_certificate.subject.country", + "type": "alias" + }, + "locality": { + "path": "tls.detailed.client_certificate.subject.locality", + "type": "alias" + }, + "organization": { + "path": "tls.detailed.client_certificate.subject.organization", + "type": "alias" + }, + "organizational_unit": { + "path": "tls.detailed.client_certificate.subject.organizational_unit", + "type": "alias" + }, + "province": { + "path": "tls.detailed.client_certificate.subject.province", + "type": "alias" + } + } + }, + "version": { + "path": "tls.detailed.client_certificate.version", + "type": "alias" + } + } + }, + "client_certificate_requested": { + "path": "tls.detailed.client_certificate_requested", + "type": "alias" + }, + "client_hello": { + "properties": { + "extensions": { + "properties": { + "_unparsed_": { + "path": "tls.detailed.client_hello.extensions._unparsed_", + "type": "alias" + }, + "application_layer_protocol_negotiation": { + "path": "tls.detailed.client_hello.extensions.application_layer_protocol_negotiation", + "type": "alias" + }, + "ec_points_formats": { + "path": "tls.detailed.client_hello.extensions.ec_points_formats", + "type": "alias" + }, + "server_name_indication": { + "path": "tls.detailed.client_hello.extensions.server_name_indication", + "type": "alias" + }, + "session_ticket": { + "path": "tls.detailed.client_hello.extensions.session_ticket", + "type": "alias" + }, + "signature_algorithms": { + "path": "tls.detailed.client_hello.extensions.signature_algorithms", + "type": "alias" + }, + "supported_groups": { + "path": "tls.detailed.client_hello.extensions.supported_groups", + "type": "alias" + }, + "supported_versions": { + "path": "tls.detailed.client_hello.extensions.supported_versions", + "type": "alias" + } + } + }, + "session_id": { + "path": "tls.detailed.client_hello.session_id", + "type": "alias" + }, + "supported_ciphers": { + "path": "tls.client.supported_ciphers", + "type": "alias" + }, + "supported_compression_methods": { + "path": "tls.detailed.client_hello.supported_compression_methods", + "type": "alias" + }, + "version": { + "path": "tls.detailed.client_hello.version", + "type": "alias" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "detailed": { + "properties": { + "alert_types": { + "ignore_above": 1024, + "type": "keyword" + }, + "client_certificate": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "type": "long" + } + } + }, + "client_certificate_requested": { + "type": "boolean" + }, + "client_hello": { + "properties": { + "extensions": { + "properties": { + "_unparsed_": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_layer_protocol_negotiation": { + "ignore_above": 1024, + "type": "keyword" + }, + "ec_points_formats": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_name_indication": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_ticket": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithms": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_groups": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_versions": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "session_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_compression_methods": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resumption_method": { + "ignore_above": 1024, + "type": "keyword" + }, + "server_certificate": { + "properties": { + "alternative_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "locality": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "province": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "type": "long" + } + } + }, + "server_certificate_chain": { + "properties": { + "issuer": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + }, + "organizational_unit": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "not_after": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_before": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "public_key_size": { + "type": "long" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "signature_algorithm": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "properties": { + "common_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country": { + "ignore_above": 1024, + "type": "keyword" + }, + "organization": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "type": "long" + } + } + }, + "server_hello": { + "properties": { + "extensions": { + "properties": { + "_unparsed_": { + "ignore_above": 1024, + "type": "keyword" + }, + "application_layer_protocol_negotiation": { + "ignore_above": 1024, + "type": "keyword" + }, + "ec_points_formats": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_ticket": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_versions": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selected_compression_method": { + "ignore_above": 1024, + "type": "keyword" + }, + "session_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "established": { + "type": "boolean" + }, + "fingerprints": { + "properties": { + "ja3": { + "path": "tls.client.ja3", + "type": "alias" + } + } + }, + "handshake_completed": { + "path": "tls.established", + "type": "alias" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "resumption_method": { + "path": "tls.detailed.resumption_method", + "type": "alias" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server_certificate": { + "properties": { + "alternative_names": { + "path": "tls.detailed.server_certificate.alternative_names", + "type": "alias" + }, + "issuer": { + "properties": { + "common_name": { + "path": "tls.detailed.server_certificate.issuer.common_name", + "type": "alias" + }, + "country": { + "path": "tls.detailed.server_certificate.issuer.country", + "type": "alias" + }, + "locality": { + "path": "tls.detailed.server_certificate.issuer.locality", + "type": "alias" + }, + "organization": { + "path": "tls.detailed.server_certificate.issuer.organization", + "type": "alias" + }, + "organizational_unit": { + "path": "tls.detailed.server_certificate.issuer.organizational_unit", + "type": "alias" + }, + "province": { + "path": "tls.detailed.server_certificate.issuer.province", + "type": "alias" + } + } + }, + "not_after": { + "path": "tls.detailed.server_certificate.not_after", + "type": "alias" + }, + "not_before": { + "path": "tls.detailed.server_certificate.not_before", + "type": "alias" + }, + "public_key_algorithm": { + "path": "tls.detailed.server_certificate.public_key_algorithm", + "type": "alias" + }, + "public_key_size": { + "path": "tls.detailed.server_certificate.public_key_size", + "type": "alias" + }, + "serial_number": { + "path": "tls.detailed.server_certificate.serial_number", + "type": "alias" + }, + "signature_algorithm": { + "path": "tls.detailed.server_certificate.signature_algorithm", + "type": "alias" + }, + "subject": { + "properties": { + "common_name": { + "path": "tls.detailed.server_certificate.subject.common_name", + "type": "alias" + }, + "country": { + "path": "tls.detailed.server_certificate.subject.country", + "type": "alias" + }, + "locality": { + "path": "tls.detailed.server_certificate.subject.locality", + "type": "alias" + }, + "organization": { + "path": "tls.detailed.server_certificate.subject.organization", + "type": "alias" + }, + "organizational_unit": { + "path": "tls.detailed.server_certificate.subject.organizational_unit", + "type": "alias" + }, + "province": { + "path": "tls.detailed.server_certificate.subject.province", + "type": "alias" + } + } + }, + "version": { + "path": "tls.detailed.server_certificate.version", + "type": "alias" + } + } + }, + "server_hello": { + "properties": { + "extensions": { + "properties": { + "_unparsed_": { + "path": "tls.detailed.server_hello.extensions._unparsed_", + "type": "alias" + }, + "application_layer_protocol_negotiation": { + "path": "tls.detailed.server_hello.extensions.application_layer_protocol_negotiation", + "type": "alias" + }, + "ec_points_formats": { + "path": "tls.detailed.server_hello.extensions.ec_points_formats", + "type": "alias" + }, + "session_ticket": { + "path": "tls.detailed.server_hello.extensions.session_ticket", + "type": "alias" + }, + "supported_versions": { + "path": "tls.detailed.server_hello.extensions.supported_versions", + "type": "alias" + } + } + }, + "selected_cipher": { + "path": "tls.cipher", + "type": "alias" + }, + "selected_compression_method": { + "path": "tls.detailed.server_hello.selected_compression_method", + "type": "alias" + }, + "session_id": { + "path": "tls.detailed.server_hello.session_id", + "type": "alias" + }, + "version": { + "path": "tls.detailed.server_hello.version", + "type": "alias" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/data.json.gz b/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/data.json.gz new file mode 100644 index 0000000000000..238fb3b6e79c0 Binary files /dev/null and b/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/mappings.json b/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/mappings.json new file mode 100644 index 0000000000000..e931063040815 --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_siem_winlogbeat/mappings.json @@ -0,0 +1,884 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_module_siem_winlogbeat", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "type": "long" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "name_map": { + "type": "object" + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selinux": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "winlog": { + "properties": { + "activity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "api": { + "ignore_above": 1024, + "type": "keyword" + }, + "channel": { + "ignore_above": 1024, + "type": "keyword" + }, + "computer_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "event_data": { + "properties": { + "ContextInfo": { + "type": "keyword" + }, + "LogonType": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "MessageNumber": { + "type": "keyword" + }, + "MessageTotal": { + "type": "keyword" + }, + "Path": { + "type": "keyword" + }, + "Payload": { + "type": "keyword" + }, + "ScriptBlockId": { + "type": "keyword" + }, + "ScriptBlockText": { + "type": "keyword" + }, + "param1": { + "type": "keyword" + }, + "param2": { + "type": "keyword" + } + } + }, + "event_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "keywords": { + "ignore_above": 1024, + "type": "keyword" + }, + "opcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "provider_guid": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "record_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "related_activity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "task": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_data": { + "type": "object" + }, + "version": { + "type": "long" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/ml/sample_logs/data.json.gz b/x-pack/test/functional/es_archives/ml/sample_logs/data.json.gz deleted file mode 100644 index 88937d484d24e..0000000000000 Binary files a/x-pack/test/functional/es_archives/ml/sample_logs/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 57f62ab33e39a..395e41a91f53a 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -16,6 +16,7 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const userMenu = getService('userMenu'); + const comboBox = getService('comboBox'); const PageObjects = getPageObjects(['common', 'header', 'settings', 'home', 'error']); interface LoginOptions { @@ -273,11 +274,7 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider async addIndexToRole(index: string) { log.debug(`Adding index ${index} to role`); - const indexInput = await retry.try(() => - find.byCssSelector('[data-test-subj="indicesInput0"] input') - ); - await indexInput.type(index); - await indexInput.type('\n'); + await comboBox.setCustom('indicesInput0', index); } async addPrivilegeToRole(privilege: string) { @@ -400,104 +397,78 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider } } - addRole(roleName: string, roleObj: Role) { + async addRole(roleName: string, roleObj: Role) { const self = this; - return ( - this.clickNewRole() - .then(function () { - // We have to use non-test-subject selectors because this markup is generated by ui-select. - log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); - return testSubjects.append('roleFormNameInput', roleName); - }) - .then(function () { - return find.setValue( - '[data-test-subj="indicesInput0"] input', - roleObj.elasticsearch.indices[0].names + '\n' - ); - }) - .then(function () { - return testSubjects.click('restrictDocumentsQuery0'); - }) - .then(function () { - if (roleObj.elasticsearch.indices[0].query) { - return testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); - } - }) - - // KibanaPrivilege - .then(async () => { - const globalPrivileges = (roleObj.kibana as any).global; - if (globalPrivileges) { - for (const privilegeName of globalPrivileges) { - const button = await testSubjects.find('addSpacePrivilegeButton'); - await button.click(); - - const spaceSelector = await testSubjects.find('spaceSelectorComboBox'); - await spaceSelector.click(); - - const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`); - await globalSpaceOption.click(); - - const basePrivilegeSelector = await testSubjects.find('basePrivilegeComboBox'); - await basePrivilegeSelector.click(); - - const privilegeOption = await find.byCssSelector(`#basePrivilege_${privilegeName}`); - await privilegeOption.click(); - - const createPrivilegeButton = await testSubjects.find('createSpacePrivilegeButton'); - await createPrivilegeButton.click(); - } - } - }) - - .then(function () { - function addPrivilege(privileges: string[]) { - return privileges.reduce(function (promise: Promise, privilegeName: string) { - // We have to use non-test-subject selectors because this markup is generated by ui-select. - return promise - .then(() => self.addPrivilegeToRole(privilegeName)) - .then(() => PageObjects.common.sleep(250)); - }, Promise.resolve()); - } - return addPrivilege(roleObj.elasticsearch.indices[0].privileges); - }) - // clicking the Granted fields and removing the asterix - .then(async function () { - function addGrantedField(field: string[]) { - return field.reduce(function (promise: Promise, fieldName: string) { - return promise - .then(function () { - return find.setValue('[data-test-subj="fieldInput0"] input', fieldName + '\n'); - }) - .then(function () { - return PageObjects.common.sleep(1000); - }); - }, Promise.resolve()); - } - - if (roleObj.elasticsearch.indices[0].field_security) { - // Toggle FLS switch - await testSubjects.click('restrictFieldsQuery0'); - - // have to remove the '*' - return find - .clickByCssSelector( - 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' - ) - .then(function () { - return addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); - }); - } - }) // clicking save button - .then(async () => { - log.debug('click save button'); - await testSubjects.click('roleFormSaveButton'); - }) - .then(function () { - return PageObjects.common.sleep(5000); - }) - ); + await this.clickNewRole(); + + // We have to use non-test-subject selectors because this markup is generated by ui-select. + log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); + await testSubjects.append('roleFormNameInput', roleName); + + for (const indexName of roleObj.elasticsearch.indices[0].names) { + await comboBox.setCustom('indicesInput0', indexName); + } + + if (roleObj.elasticsearch.indices[0].query) { + await testSubjects.click('restrictDocumentsQuery0'); + await testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); + } + + const globalPrivileges = (roleObj.kibana as any).global; + if (globalPrivileges) { + for (const privilegeName of globalPrivileges) { + await testSubjects.click('addSpacePrivilegeButton'); + + await testSubjects.click('spaceSelectorComboBox'); + + const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`); + await globalSpaceOption.click(); + + await testSubjects.click('basePrivilegeComboBox'); + + const privilegeOption = await find.byCssSelector(`#basePrivilege_${privilegeName}`); + await privilegeOption.click(); + + await testSubjects.click('createSpacePrivilegeButton'); + } + } + + function addPrivilege(privileges: string[]) { + return privileges.reduce(function (promise: Promise, privilegeName: string) { + return promise + .then(() => self.addPrivilegeToRole(privilegeName)) + .then(() => PageObjects.common.sleep(250)); + }, Promise.resolve()); + } + + await addPrivilege(roleObj.elasticsearch.indices[0].privileges); + + async function addGrantedField(fields: string[]) { + for (const entry of fields) { + await comboBox.setCustom('fieldInput0', entry); + } + } + + // clicking the Granted fields and removing the asterix + if (roleObj.elasticsearch.indices[0].field_security) { + // Toggle FLS switch + await testSubjects.click('restrictFieldsQuery0'); + + // have to remove the '*' + await find.clickByCssSelector( + 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' + ); + + await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); + } + + log.debug('click save button'); + await testSubjects.click('roleFormSaveButton'); + + // Signifies that the role management page redirected back to the role grid page, + // and successfully refreshed the grid + await testSubjects.existOrFail('roleRow'); } async selectRole(role: string) { diff --git a/x-pack/test/functional/services/dashboard/drilldowns_manage.ts b/x-pack/test/functional/services/dashboard/drilldowns_manage.ts index 1710cb8bfb71a..a01fde3a5233f 100644 --- a/x-pack/test/functional/services/dashboard/drilldowns_manage.ts +++ b/x-pack/test/functional/services/dashboard/drilldowns_manage.ts @@ -20,8 +20,22 @@ export function DashboardDrilldownsManageProvider({ getService }: FtrProviderCon const testSubjects = getService('testSubjects'); const flyout = getService('flyout'); const comboBox = getService('comboBox'); + const esArchiver = getService('esArchiver'); return new (class DashboardDrilldownsManage { + readonly DASHBOARD_WITH_PIE_CHART_NAME = 'Dashboard with Pie Chart'; + readonly DASHBOARD_WITH_AREA_CHART_NAME = 'Dashboard With Area Chart'; + + async loadData() { + log.debug('loadData'); + await esArchiver.load('dashboard/drilldowns'); + } + + async unloadData() { + log.debug('unloadData'); + await esArchiver.unload('dashboard/drilldowns'); + } + async expectsCreateDrilldownFlyoutOpen() { log.debug('expectsCreateDrilldownFlyoutOpen'); await testSubjects.existOrFail(CREATE_DRILLDOWN_FLYOUT_DATA_TEST_SUBJ); diff --git a/x-pack/test/functional/services/dashboard/index.ts b/x-pack/test/functional/services/dashboard/index.ts index dee525fa0a388..494289c4dd2cb 100644 --- a/x-pack/test/functional/services/dashboard/index.ts +++ b/x-pack/test/functional/services/dashboard/index.ts @@ -6,3 +6,4 @@ export { DashboardDrilldownPanelActionsProvider } from './panel_drilldown_actions'; export { DashboardDrilldownsManageProvider } from './drilldowns_manage'; +export { DashboardPanelTimeRangeProvider } from './panel_time_range'; diff --git a/x-pack/test/functional/services/dashboard/panel_time_range.ts b/x-pack/test/functional/services/dashboard/panel_time_range.ts new file mode 100644 index 0000000000000..6a91a6ff0584b --- /dev/null +++ b/x-pack/test/functional/services/dashboard/panel_time_range.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 { FtrProviderContext } from '../../ftr_provider_context'; +import { CommonlyUsed } from '../../../../../test/functional/page_objects/time_picker'; + +export function DashboardPanelTimeRangeProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + + return new (class DashboardPanelTimeRange { + public readonly MODAL_TEST_SUBJ = 'customizeTimeRangeModal'; + public readonly CUSTOM_TIME_RANGE_ACTION = 'CUSTOM_TIME_RANGE'; + + public async clickTimeRangeActionInContextMenu() { + log.debug('clickTimeRangeActionInContextMenu'); + await testSubjects.click('embeddablePanelAction-CUSTOM_TIME_RANGE'); + } + + public async findModal() { + log.debug('findModal'); + return await testSubjects.find(this.MODAL_TEST_SUBJ); + } + + public async findModalTestSubject(testSubject: string) { + log.debug('findModalElement'); + const modal = await this.findModal(); + return await modal.findByCssSelector(`[data-test-subj="${testSubject}"]`); + } + + public async findToggleQuickMenuButton() { + log.debug('findToggleQuickMenuButton'); + return await this.findModalTestSubject('superDatePickerToggleQuickMenuButton'); + } + + public async clickToggleQuickMenuButton() { + log.debug('clickToggleQuickMenuButton'); + const button = await this.findToggleQuickMenuButton(); + await button.click(); + } + + public async clickCommonlyUsedTimeRange(time: CommonlyUsed) { + log.debug('clickCommonlyUsedTimeRange', time); + await testSubjects.click(`superDatePickerCommonlyUsed_${time}`); + } + + public async clickModalPrimaryButton() { + log.debug('clickModalPrimaryButton'); + const button = await this.findModalTestSubject('addPerPanelTimeRangeButton'); + await button.click(); + } + })(); +} diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index f1d84f3054aa0..4b6342758be93 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -52,6 +52,7 @@ import { TransformProvider } from './transform'; import { DashboardDrilldownPanelActionsProvider, DashboardDrilldownsManageProvider, + DashboardPanelTimeRangeProvider, } from './dashboard'; // define the name and providers for services that should be @@ -97,4 +98,5 @@ export const services = { transform: TransformProvider, dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, dashboardDrilldownsManage: DashboardDrilldownsManageProvider, + dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, }; diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts index 6ec72c76bb9cf..7c479a4234673 100644 --- a/x-pack/test/functional/services/ml/anomaly_explorer.ts +++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts @@ -66,5 +66,38 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid async assertSwimlaneViewByExists() { await testSubjects.existOrFail('mlAnomalyExplorerSwimlaneViewBy'); }, + + async openAddToDashboardControl() { + await testSubjects.click('mlAnomalyTimelinePanelMenu'); + await testSubjects.click('mlAnomalyTimelinePanelAddToDashboardButton'); + await testSubjects.existOrFail('mlAddToDashboardModal'); + }, + + async addAndEditSwimlaneInDashboard(dashboardTitle: string) { + await this.filterWithSearchString(dashboardTitle); + await testSubjects.isDisplayed('mlDashboardSelectionTable > checkboxSelectAll'); + await testSubjects.click('mlDashboardSelectionTable > checkboxSelectAll'); + expect(await testSubjects.isChecked('mlDashboardSelectionTable > checkboxSelectAll')).to.be( + true + ); + await testSubjects.clickWhenNotDisabled('mlAddAndEditDashboardButton'); + const embeddable = await testSubjects.find('mlAnomalySwimlaneEmbeddableWrapper'); + const swimlane = await embeddable.findByClassName('ml-swimlanes'); + expect(await swimlane.isDisplayed()).to.eql( + true, + 'Anomaly swimlane should be displayed in dashboard' + ); + }, + + async waitForDashboardsToLoad() { + await testSubjects.existOrFail('~mlDashboardSelectionTable', { timeout: 60 * 1000 }); + }, + + async filterWithSearchString(filter: string) { + await this.waitForDashboardsToLoad(); + const searchBarInput = await testSubjects.find('mlDashboardsSearchBox'); + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.type(filter); + }, }; } diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 739fd844f1193..9927c987bbea5 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -5,7 +5,7 @@ */ import { ProvidedType } from '@kbn/test/types/ftr'; -import { savedSearches } from './test_resources_data'; +import { savedSearches, dashboards } from './test_resources_data'; import { COMMON_REQUEST_HEADERS } from './common'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -36,11 +36,20 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider await kibanaServer.uiSettings.unset('dateFormat:tz'); }, - async savedObjectExists(id: string, objectType: SavedObjectType): Promise { + async savedObjectExistsById(id: string, objectType: SavedObjectType): Promise { const response = await supertest.get(`/api/saved_objects/${objectType}/${id}`); return response.status === 200; }, + async savedObjectExistsByTitle(title: string, objectType: SavedObjectType): Promise { + const id = await this.getSavedObjectIdByTitle(title, objectType); + if (id) { + return await this.savedObjectExistsById(id, objectType); + } else { + return false; + } + }, + async getSavedObjectIdByTitle( title: string, objectType: SavedObjectType @@ -70,6 +79,14 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider return this.getSavedObjectIdByTitle(title, SavedObjectType.SEARCH); }, + async getVisualizationId(title: string): Promise { + return this.getSavedObjectIdByTitle(title, SavedObjectType.VISUALIZATION); + }, + + async getDashboardId(title: string): Promise { + return this.getSavedObjectIdByTitle(title, SavedObjectType.DASHBOARD); + }, + async createIndexPattern(title: string, timeFieldName?: string): Promise { log.debug( `Creating index pattern with title '${title}'${ @@ -84,6 +101,8 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider .expect(200) .then((res: any) => res.body); + await this.assertIndexPatternExistByTitle(title); + log.debug(` > Created with id '${createResponse.id}'`); return createResponse.id; }, @@ -99,18 +118,7 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider }, async assertIndexPatternNotExist(title: string) { - await retry.waitForWithTimeout( - `index pattern '${title}' to not exist`, - 5 * 1000, - async () => { - const indexPatternId = await this.getIndexPatternId(title); - if (!indexPatternId) { - return true; - } else { - throw new Error(`Index pattern '${title}' should not exist.`); - } - } - ); + await this.assertSavedObjectNotExistsByTitle(title, SavedObjectType.INDEX_PATTERN); }, async createSavedSearch(title: string, body: object): Promise { @@ -123,6 +131,22 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider .expect(200) .then((res: any) => res.body); + await this.assertSavedSearchExistByTitle(title); + + log.debug(` > Created with id '${createResponse.id}'`); + return createResponse.id; + }, + + async createDashboard(title: string, body: object): Promise { + log.debug(`Creating dashboard with title '${title}'`); + + const createResponse = await supertest + .post(`/api/saved_objects/${SavedObjectType.DASHBOARD}`) + .set(COMMON_REQUEST_HEADERS) + .send(body) + .expect(200) + .then((res: any) => res.body); + log.debug(` > Created with id '${createResponse.id}'`); return createResponse.id; }, @@ -171,6 +195,21 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider await this.createSavedSearchIfNeeded(savedSearches.farequoteFilter); }, + async createMLTestDashboardIfNeeded() { + await this.createDashboardIfNeeded(dashboards.mlTestDashboard); + }, + + async createDashboardIfNeeded(dashboard: any) { + const title = dashboard.requestBody.attributes.title; + const dashboardId = await this.getDashboardId(title); + if (dashboardId !== undefined) { + log.debug(`Dashboard with title '${title}' already exists. Nothing to create.`); + return dashboardId; + } else { + return await this.createDashboard(title, dashboard.requestBody); + } + }, + async createSavedSearchFarequoteLuceneIfNeeded() { await this.createSavedSearchIfNeeded(savedSearches.farequoteLucene); }, @@ -187,24 +226,41 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider await this.createSavedSearchIfNeeded(savedSearches.farequoteFilterAndKuery); }, - async deleteIndexPattern(title: string) { - log.debug(`Deleting index pattern with title '${title}'...`); + async deleteSavedObjectById(id: string, objectType: SavedObjectType) { + log.debug(`Deleting ${objectType} with id '${id}'...`); - const indexPatternId = await this.getIndexPatternId(title); - if (indexPatternId === undefined) { - log.debug(`Index pattern with title '${title}' does not exists. Nothing to delete.`); + if ((await this.savedObjectExistsById(id, objectType)) === false) { + log.debug(`${objectType} with id '${id}' does not exists. Nothing to delete.`); return; } else { await supertest - .delete(`/api/saved_objects/${SavedObjectType.INDEX_PATTERN}/${indexPatternId}`) + .delete(`/api/saved_objects/${objectType}/${id}`) .set(COMMON_REQUEST_HEADERS) .expect(200); - log.debug(` > Deleted index pattern with id '${indexPatternId}'`); + await this.assertSavedObjectNotExistsById(id, objectType); + + log.debug(` > Deleted ${objectType} with id '${id}'`); + } + }, + + async deleteIndexPatternByTitle(title: string) { + log.debug(`Deleting index pattern with title '${title}'...`); + + const indexPatternId = await this.getIndexPatternId(title); + if (indexPatternId === undefined) { + log.debug(`Index pattern with title '${title}' does not exists. Nothing to delete.`); + return; + } else { + await this.deleteIndexPatternById(indexPatternId); } }, - async deleteSavedSearch(title: string) { + async deleteIndexPatternById(id: string) { + await this.deleteSavedObjectById(id, SavedObjectType.INDEX_PATTERN); + }, + + async deleteSavedSearchByTitle(title: string) { log.debug(`Deleting saved search with title '${title}'...`); const savedSearchId = await this.getSavedSearchId(title); @@ -212,19 +268,144 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider log.debug(`Saved search with title '${title}' does not exists. Nothing to delete.`); return; } else { - await supertest - .delete(`/api/saved_objects/${SavedObjectType.SEARCH}/${savedSearchId}`) - .set(COMMON_REQUEST_HEADERS) - .expect(200); + await this.deleteSavedSearchById(savedSearchId); + } + }, + + async deleteSavedSearchById(id: string) { + await this.deleteSavedObjectById(id, SavedObjectType.SEARCH); + }, + + async deleteVisualizationByTitle(title: string) { + log.debug(`Deleting visualization with title '${title}'...`); - log.debug(` > Deleted saved searchwith id '${savedSearchId}'`); + const visualizationId = await this.getVisualizationId(title); + if (visualizationId === undefined) { + log.debug(`Visualization with title '${title}' does not exists. Nothing to delete.`); + return; + } else { + await this.deleteVisualizationById(visualizationId); } }, + async deleteVisualizationById(id: string) { + await this.deleteSavedObjectById(id, SavedObjectType.VISUALIZATION); + }, + + async deleteDashboardByTitle(title: string) { + log.debug(`Deleting dashboard with title '${title}'...`); + + const dashboardId = await this.getDashboardId(title); + if (dashboardId === undefined) { + log.debug(`Dashboard with title '${title}' does not exists. Nothing to delete.`); + return; + } else { + await this.deleteDashboardById(dashboardId); + } + }, + + async deleteDashboardById(id: string) { + await this.deleteSavedObjectById(id, SavedObjectType.DASHBOARD); + }, + async deleteSavedSearches() { for (const search of Object.values(savedSearches)) { - await this.deleteSavedSearch(search.requestBody.attributes.title); + await this.deleteSavedSearchByTitle(search.requestBody.attributes.title); + } + }, + + async deleteDashboards() { + for (const dashboard of Object.values(dashboards)) { + await this.deleteDashboardByTitle(dashboard.requestBody.attributes.title); } }, + + async assertSavedObjectExistsByTitle(title: string, objectType: SavedObjectType) { + await retry.waitForWithTimeout( + `${objectType} with title '${title}' to exist`, + 5 * 1000, + async () => { + if ((await this.savedObjectExistsByTitle(title, objectType)) === true) { + return true; + } else { + throw new Error(`${objectType} with title '${title}' should exist.`); + } + } + ); + }, + + async assertSavedObjectNotExistsByTitle(title: string, objectType: SavedObjectType) { + await retry.waitForWithTimeout( + `${objectType} with title '${title}' not to exist`, + 5 * 1000, + async () => { + if ((await this.savedObjectExistsByTitle(title, objectType)) === false) { + return true; + } else { + throw new Error(`${objectType} with title '${title}' should not exist.`); + } + } + ); + }, + + async assertSavedObjectExistsById(id: string, objectType: SavedObjectType) { + await retry.waitForWithTimeout( + `${objectType} with id '${id}' to exist`, + 5 * 1000, + async () => { + if ((await this.savedObjectExistsById(id, objectType)) === true) { + return true; + } else { + throw new Error(`${objectType} with id '${id}' should exist.`); + } + } + ); + }, + + async assertSavedObjectNotExistsById(id: string, objectType: SavedObjectType) { + await retry.waitForWithTimeout( + `${objectType} with id '${id}' not to exist`, + 5 * 1000, + async () => { + if ((await this.savedObjectExistsById(id, objectType)) === false) { + return true; + } else { + throw new Error(`${objectType} with id '${id}' should not exist.`); + } + } + ); + }, + + async assertIndexPatternExistByTitle(title: string) { + await this.assertSavedObjectExistsByTitle(title, SavedObjectType.INDEX_PATTERN); + }, + + async assertIndexPatternExistById(id: string) { + await this.assertSavedObjectExistsById(id, SavedObjectType.INDEX_PATTERN); + }, + + async assertSavedSearchExistByTitle(title: string) { + await this.assertSavedObjectExistsByTitle(title, SavedObjectType.SEARCH); + }, + + async assertSavedSearchExistById(id: string) { + await this.assertSavedObjectExistsById(id, SavedObjectType.SEARCH); + }, + + async assertVisualizationExistByTitle(title: string) { + await this.assertSavedObjectExistsByTitle(title, SavedObjectType.VISUALIZATION); + }, + + async assertVisualizationExistById(id: string) { + await this.assertSavedObjectExistsById(id, SavedObjectType.VISUALIZATION); + }, + + async assertDashboardExistByTitle(title: string) { + await this.assertSavedObjectExistsByTitle(title, SavedObjectType.DASHBOARD); + }, + + async assertDashboardExistById(id: string) { + await this.assertSavedObjectExistsById(id, SavedObjectType.DASHBOARD); + }, }; } diff --git a/x-pack/test/functional/services/ml/test_resources_data.ts b/x-pack/test/functional/services/ml/test_resources_data.ts index dd600077182f9..2ab1f4de54228 100644 --- a/x-pack/test/functional/services/ml/test_resources_data.ts +++ b/x-pack/test/functional/services/ml/test_resources_data.ts @@ -247,3 +247,22 @@ export const savedSearches = { }, }, }; + +export const dashboards = { + mlTestDashboard: { + requestBody: { + attributes: { + title: 'ML Test', + hits: 0, + description: '', + panelsJSON: '[]', + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: false, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, + }, + }, + }, +}; diff --git a/x-pack/test/functional/services/uptime/common.ts b/x-pack/test/functional/services/uptime/common.ts index b5be1e29a0e8c..5f544b5e46010 100644 --- a/x-pack/test/functional/services/uptime/common.ts +++ b/x-pack/test/functional/services/uptime/common.ts @@ -58,13 +58,13 @@ export function UptimeCommonProvider({ getService }: FtrProviderContext) { '[data-test-subj="xpack.uptime.filterBar.filterStatusUp"]' ); if (await upFilter.elementHasClass('euiFilterButton-hasActiveFilters')) { - this.setStatusFilterUp(); + await this.setStatusFilterUp(); } const downFilter = await find.byCssSelector( '[data-test-subj="xpack.uptime.filterBar.filterStatusDown"]' ); if (await downFilter.elementHasClass('euiFilterButton-hasActiveFilters')) { - this.setStatusFilterDown(); + await this.setStatusFilterDown(); } }, async selectFilterItem(filterType: string, option: string) { diff --git a/x-pack/test/functional/services/uptime/monitor.ts b/x-pack/test/functional/services/uptime/monitor.ts index 3137711698ba4..593950fbb7619 100644 --- a/x-pack/test/functional/services/uptime/monitor.ts +++ b/x-pack/test/functional/services/uptime/monitor.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect/expect.js'; import { FtrProviderContext } from '../../ftr_provider_context'; export function UptimeMonitorProvider({ getService }: FtrProviderContext) { @@ -17,6 +18,13 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) { timeout: 3000, }); }, + async displayOverallAvailability(availabilityVal: string) { + return retry.tryForTime(60 * 1000, async () => { + await testSubjects.existOrFail('uptimeOverallAvailability'); + const availability = await testSubjects.getVisibleText('uptimeOverallAvailability'); + expect(availability).to.be(availabilityVal); + }); + }, async locationMapIsRendered() { return retry.tryForTime(15000, async () => { await testSubjects.existOrFail('xpack.uptime.locationMap.embeddedPanel', { @@ -45,5 +53,8 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) { ); }); }, + async toggleToMapView() { + await testSubjects.click('uptimeMonitorToggleMapBtn'); + }, }; } diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index 7c5a4632f8627..f8e0c0cff41f4 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -56,6 +56,7 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToMonitor: async (monitorId: string) => { + // only go to monitor page if not already there if (!(await testSubjects.exists('uptimeMonitorPage', { timeout: 0 }))) { await testSubjects.click(`monitor-page-link-${monitorId}`); await testSubjects.existOrFail('uptimeMonitorPage', { @@ -80,5 +81,13 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv await PageObjects.timePicker.setAbsoluteRange(dateStart, dateEnd); await this.goToMonitor(monitorId); }, + + async isOnDetailsPage() { + return await testSubjects.exists('uptimeMonitorPage', { timeout: 0 }); + }, + + async goToHomeViaBreadCrumb() { + await testSubjects.click('breadcrumb first'); + }, }; } diff --git a/x-pack/test/functional_embedded/config.firefox.ts b/x-pack/test/functional_embedded/config.firefox.ts new file mode 100644 index 0000000000000..2051d1afd4ab3 --- /dev/null +++ b/x-pack/test/functional_embedded/config.firefox.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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const chromeConfig = await readConfigFile(require.resolve('./config')); + + return { + ...chromeConfig.getAll(), + + browser: { + type: 'firefox', + acceptInsecureCerts: true, + }, + + suiteTags: { + exclude: ['skipFirefox'], + }, + + junit: { + reportName: 'Firefox Kibana Embedded in iframe with X-Pack Security', + }, + }; +} diff --git a/x-pack/test/functional_embedded/config.ts b/x-pack/test/functional_embedded/config.ts new file mode 100644 index 0000000000000..95b290ece7db2 --- /dev/null +++ b/x-pack/test/functional_embedded/config.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Fs from 'fs'; +import { resolve } from 'path'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const iframeEmbeddedPlugin = resolve(__dirname, './plugins/iframe_embedded'); + + const servers = { + ...kibanaFunctionalConfig.get('servers'), + elasticsearch: { + ...kibanaFunctionalConfig.get('servers.elasticsearch'), + }, + kibana: { + ...kibanaFunctionalConfig.get('servers.kibana'), + protocol: 'https', + ssl: { + enabled: true, + key: Fs.readFileSync(KBN_KEY_PATH).toString('utf8'), + certificate: Fs.readFileSync(KBN_CERT_PATH).toString('utf8'), + certificateAuthorities: Fs.readFileSync(CA_CERT_PATH).toString('utf8'), + }, + }, + }; + + return { + testFiles: [require.resolve('./tests')], + servers, + services: kibanaFunctionalConfig.get('services'), + pageObjects, + browser: { + acceptInsecureCerts: true, + }, + junit: { + reportName: 'Kibana Embedded in iframe with X-Pack Security', + }, + + esTestCluster: kibanaFunctionalConfig.get('esTestCluster'), + apps: { + ...kibanaFunctionalConfig.get('apps'), + }, + + kbnTestServer: { + ...kibanaFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), + `--plugin-path=${iframeEmbeddedPlugin}`, + '--server.ssl.enabled=true', + `--server.ssl.key=${KBN_KEY_PATH}`, + `--server.ssl.certificate=${KBN_CERT_PATH}`, + `--server.ssl.certificateAuthorities=${CA_CERT_PATH}`, + + '--xpack.security.sameSiteCookies=None', + '--xpack.security.secureCookies=true', + ], + }, + }; +} diff --git a/x-pack/test/functional_embedded/ftr_provider_context.d.ts b/x-pack/test/functional_embedded/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..5646c06a3cd30 --- /dev/null +++ b/x-pack/test/functional_embedded/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { pageObjects }; diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json b/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json new file mode 100644 index 0000000000000..ea9f55bd21c6e --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "iframe_embedded", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false +} diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json b/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json new file mode 100644 index 0000000000000..9fa1554e5312b --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json @@ -0,0 +1,14 @@ +{ + "name": "iframe_embedded", + "version": "0.0.0", + "kibana": { + "version": "kibana" + }, + "scripts": { + "kbn": "node ../../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.9.5" + } +} diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts new file mode 100644 index 0000000000000..976ef19d4d8a7 --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/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 { PluginInitializerContext } from 'kibana/server'; +import { IframeEmbeddedPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new IframeEmbeddedPlugin(initContext); diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts new file mode 100644 index 0000000000000..890fe14cf03cf --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Url from 'url'; +import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; + +function renderBody(iframeUrl: string) { + return ` + + + + + Kibana embedded in iframe + + +