diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 27532f0f377f9a..96670b5d5107b0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,6 +12,7 @@ /src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app +/src/plugins/lens_oss/ @elastic/kibana-app /src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app diff --git a/.i18nrc.json b/.i18nrc.json index 68e38d3976a68b..653c67b535bff7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -59,6 +59,8 @@ "visTypeVislib": "src/plugins/vis_type_vislib", "visTypeXy": "src/plugins/vis_type_xy", "visualizations": "src/plugins/visualizations", + "lensOss": "src/plugins/lens_oss", + "mapsOss": "src/plugins/maps_oss", "visualize": "src/plugins/visualize", "apmOss": "src/plugins/apm_oss", "usageCollection": "src/plugins/usage_collection" diff --git a/.sass-lint.yml b/.sass-lint.yml index 9eed50602f5205..85599750b0cb8d 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -6,6 +6,7 @@ files: - 'src/plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'src/plugins/vis_type_vega/**/*.s+(a|c)ss' - 'src/plugins/vis_type_xy/**/*.s+(a|c)ss' + - 'src/plugins/visualizations/public/wizard/**/*.s+(a|c)ss' - 'x-pack/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' - 'x-pack/plugins/lens/**/*.s+(a|c)ss' diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b59545cbb85a64..3c62c1fbca9825 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -130,6 +130,11 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |The legacyExport plugin adds support for the legacy saved objects export format. +|{kib-repo}blob/{branch}/src/plugins/lens_oss/README.md[lensOss] +|The lens_oss plugin registers the lens visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + + |{kib-repo}blob/{branch}/src/plugins/management/README.md[management] |This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual managment section into it. This plugin does not contain any individual @@ -140,6 +145,11 @@ management section itself. |Internal objects used by the Coordinate, Region, and Vega visualizations. +|{kib-repo}blob/{branch}/src/plugins/maps_oss/README.md[mapsOss] +|The maps_oss plugin registers the maps visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + + |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] |The navigation plugins exports the TopNavMenu component. It also provides a stateful version of it on the start contract. diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 3afd5eaa6f1f7d..9da31bb16b56b6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -9,6 +9,7 @@ ```typescript readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 5249381969b98d..01504aafe3bae9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: 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 addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly 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 addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly visualize: Record<string, string>;
} | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md index 32a7151578658c..8cc32ff698b386 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md @@ -7,5 +7,5 @@ Signature: ```typescript -ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element +ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md index e4980ce04b9e27..92ea071b23dfce 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md @@ -18,6 +18,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams | [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[] | | | [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number | | | [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression | | +| [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) | <TData, TInspectorAdapters>(data: TData, adapters?: TInspectorAdapters) => void | | | [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void | | | [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl' | | | [reload$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.reload_.md) | Observable<unknown> | An observable which can be used to re-run the expression without destroying the component | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md new file mode 100644 index 00000000000000..05ddb0b13a5bee --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ReactExpressionRendererProps](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) > [onData$](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.ondata_.md) + +## ReactExpressionRendererProps.onData$ property + +Signature: + +```typescript +onData$?: (data: TData, adapters?: TInspectorAdapters) => void; +``` diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index b32c340df4adfd..79fa9a642428af 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -43,6 +43,9 @@ Changing these settings may disable features of the APM App. | `xpack.apm.enabled` | Set to `false` to disable the APM app. Defaults to `true`. +| `xpack.apm.maxServiceEnvironments` + | Maximum number of unique service environments recognized by the UI. Defaults to `100`. + | `xpack.apm.serviceMapFingerprintBucketSize` | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 6b01094f7248a0..12043ead28d555 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -92,8 +92,8 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend `..icon` {ess-icon} | Custom icon for the provider entry displayed on the Login Selector UI. -| `xpack.security.authc.providers.` -`..showInSelector` {ess-icon} +| `xpack.security.authc.providers..` +`.showInSelector` {ess-icon} | Flag that indicates if the provider should have an entry on the Login Selector UI. Setting this to `false` doesn't remove the provider from the authentication chain. 2+a| @@ -103,10 +103,31 @@ The valid settings in the `xpack.security.authc.providers` namespace vary depend You are unable to set this setting to `false` for `basic` and `token` authentication providers. ============ -| `xpack.security.authc.providers.` -`..accessAgreement.message` {ess-icon} +| `xpack.security.authc.providers..` +`.accessAgreement.message` {ess-icon} | Access agreement text in Markdown format. For more information, refer to <>. +| [[xpack-security-provider-session-idleTimeout]] `xpack.security.authc.providers..` +`.session.idleTimeout` {ess-icon} +| Ensures that user sessions will expire after a period of inactivity. Setting this to `0` will prevent sessions from expiring because of inactivity. By default, this setting is equal to <>. + +2+a| +[TIP] +============ +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +============ + +| [[xpack-security-provider-session-lifespan]] `xpack.security.authc.providers..` +`.session.lifespan` {ess-icon} +| Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is set to `0`, user sessions could stay active indefinitely. By default, this setting is equal to <>. + +2+a| +[TIP] +============ +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +============ + |=== [float] @@ -210,32 +231,32 @@ You can configure the following settings in the `kibana.yml` file. |[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} | Ensures that user sessions will expire after a period of inactivity. This and <> are both -highly recommended. By default, this setting is not set. +highly recommended. You can also specify this setting for <>. If this is _not_ set or set to `0`, then sessions will never expire due to inactivity. By default, this setting is not set. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ |[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} - | Ensures that user sessions will expire after the defined time period. This behavior also known as an "absolute timeout". If -this is _not_ set, user sessions could stay active indefinitely. This and <> are both highly -recommended. By default, this setting is not set. + | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If +this is _not_ set or set to `0`, user sessions could stay active indefinitely. This and <> are both highly +recommended. You can also specify this setting for <>. By default, this setting is not set. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ -| `xpack.security.session.cleanupInterval` +| `xpack.security.session.cleanupInterval` {ess-icon} | Sets the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour. The minimum value is 10 seconds. 2+a| [TIP] ============ -The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). +Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w'). ============ |=== diff --git a/package.json b/package.json index 1c218307b35c39..ade567c840da77 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "**/minimist": "^1.2.5", "**/node-jose/node-forge": "^0.10.0", "**/request": "^2.88.2", + "**/trim": "0.0.3", "**/typescript": "4.0.2" }, "engines": { @@ -236,6 +237,7 @@ "markdown-it": "^10.0.0", "md5": "^2.1.0", "mime": "^2.4.4", + "mime-types": "^2.1.27", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", "moment": "^2.24.0", @@ -842,15 +844,13 @@ "vinyl-fs": "^3.0.3", "wait-on": "^5.0.1", "watchpack": "^1.6.0", - "webpack-cli": "^3.3.10", - "webpack-dev-server": "^3.8.2", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0", "webpack-merge": "^4.2.2", "write-pkg": "^4.0.0", "xml-crypto": "^2.0.0", "xmlbuilder": "13.0.2", "yargs": "^15.4.1", - "yeoman-generator": "1.1.1", - "yo": "2.0.6", "zlib": "^1.0.5" } -} \ No newline at end of file +} diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index 3910ee3235cafe..64b02f104a1f1f 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -18,7 +18,7 @@ */ import { ConfigDeprecationLogger } from './types'; -import { configDeprecationFactory } from './deprecation_factory'; +import { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; @@ -250,6 +250,89 @@ describe('DeprecationFactory', () => { }); }); + describe('copyFromRoot', () => { + it('copies a property to a different namespace', () => { + const rawConfig = { + originplugin: { + deprecated: 'toberenamed', + valid: 'valid', + }, + destinationplugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('originplugin.deprecated', 'destinationplugin.renamed')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + originplugin: { + deprecated: 'toberenamed', + valid: 'valid', + }, + destinationplugin: { + renamed: 'toberenamed', + property: 'value', + }, + }); + expect(deprecationMessages.length).toEqual(0); + }); + + it('does not alter config if origin property is not present', () => { + const rawConfig = { + myplugin: { + new: 'new', + valid: 'valid', + }, + someOtherPlugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('myplugin.deprecated', 'myplugin.new')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + myplugin: { + new: 'new', + valid: 'valid', + }, + someOtherPlugin: { + property: 'value', + }, + }); + expect(deprecationMessages.length).toEqual(0); + }); + + it('does not alter config if they both exist', () => { + const rawConfig = { + myplugin: { + deprecated: 'deprecated', + renamed: 'renamed', + }, + someOtherPlugin: { + property: 'value', + }, + }; + const processed = copyFromRoot('myplugin.deprecated', 'someOtherPlugin.property')( + rawConfig, + 'does-not-matter', + logger + ); + expect(processed).toEqual({ + myplugin: { + deprecated: 'deprecated', + renamed: 'renamed', + }, + someOtherPlugin: { + property: 'value', + }, + }); + }); + }); + describe('unused', () => { it('removes the unused property from the config and logs a warning is present', () => { const rawConfig = { diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 0598347d2cffcc..70a55fedf05bea 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -56,6 +56,26 @@ const _rename = ( return config; }; +const _copy = ( + config: Record, + rootPath: string, + originKey: string, + destinationKey: string +) => { + const originPath = getPath(rootPath, originKey); + const originValue = get(config, originPath); + if (originValue === undefined) { + return config; + } + + const destinationPath = getPath(rootPath, destinationKey); + const destinationValue = get(config, destinationPath); + if (destinationValue === undefined) { + set(config, destinationPath, originValue); + } + return config; +}; + const _unused = ( config: Record, rootPath: string, @@ -80,6 +100,12 @@ const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): Confi log ) => _rename(config, '', log, oldKey, newKey, silent); +export const copyFromRoot = (originKey: string, destinationKey: string): ConfigDeprecation => ( + config, + rootPath, + log +) => _copy(config, '', originKey, destinationKey); + const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => _unused(config, rootPath, log, unusedKey); diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 504dbfeeb001a4..4609b7b1d62d0d 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -24,5 +24,5 @@ export { ConfigDeprecationFactory, ConfigDeprecationProvider, } from './types'; -export { configDeprecationFactory } from './deprecation_factory'; +export { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 68609c6d5c7c31..b834568e8b3ae7 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -25,6 +25,7 @@ export { ConfigDeprecationLogger, ConfigDeprecationProvider, ConfigDeprecationWithContext, + copyFromRoot, } from './deprecation'; export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 11e977c74cf22f..701b7cab21600d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -45,6 +45,7 @@ pageLoadAssetSize: kibanaReact: 161921 kibanaUtils: 198829 lens: 96624 + lensOss: 19341 licenseManagement: 41817 licensing: 39008 lists: 183665 @@ -53,6 +54,7 @@ pageLoadAssetSize: maps: 183610 mapsLegacy: 116817 mapsLegacyLicensing: 20214 + mapsOss: 19284 ml: 82187 monitoring: 50000 navigation: 37269 diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index a780ec96dd9564..b7d9803059aa89 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__(506); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(505); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); @@ -106,7 +106,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -150,7 +150,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(499); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(498); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -8897,9 +8897,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(365); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(398); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(399); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(397); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(398); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8942,10 +8942,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(357); -/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(362); -/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(359); -/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(363); +/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(358); +/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(363); +/* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(360); +/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(364); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -32193,7 +32193,7 @@ __webpack_require__.r(__webpack_exports__); /* 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__(314); /* 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__(349); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(350); /* 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__(246); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } @@ -32289,13 +32289,13 @@ const childProcess = __webpack_require__(315); const crossSpawn = __webpack_require__(316); const stripFinalNewline = __webpack_require__(329); const npmRunPath = __webpack_require__(330); -const onetime = __webpack_require__(331); -const makeError = __webpack_require__(333); -const normalizeStdio = __webpack_require__(338); -const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(339); -const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(340); -const {mergePromise, getSpawnedPromise} = __webpack_require__(347); -const {joinCommand, parseCommand} = __webpack_require__(348); +const onetime = __webpack_require__(332); +const makeError = __webpack_require__(334); +const normalizeStdio = __webpack_require__(339); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(340); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(341); +const {mergePromise, getSpawnedPromise} = __webpack_require__(348); +const {joinCommand, parseCommand} = __webpack_require__(349); const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; @@ -33274,7 +33274,7 @@ module.exports = input => { "use strict"; const path = __webpack_require__(4); -const pathKey = __webpack_require__(323); +const pathKey = __webpack_require__(331); const npmRunPath = options => { options = { @@ -33327,7 +33327,30 @@ module.exports.env = options => { "use strict"; -const mimicFn = __webpack_require__(332); + +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; + + if (platform !== 'win32') { + return 'PATH'; + } + + return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; +}; + +module.exports = pathKey; +// TODO: Remove this for the next major release +module.exports.default = pathKey; + + +/***/ }), +/* 332 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const mimicFn = __webpack_require__(333); const calledFunctions = new WeakMap(); @@ -33379,7 +33402,7 @@ module.exports.callCount = fn => { /***/ }), -/* 332 */ +/* 333 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33399,12 +33422,12 @@ module.exports.default = mimicFn; /***/ }), -/* 333 */ +/* 334 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {signalsByName} = __webpack_require__(334); +const {signalsByName} = __webpack_require__(335); const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { if (timedOut) { @@ -33492,14 +33515,14 @@ module.exports = makeError; /***/ }), -/* 334 */ +/* 335 */ /***/ (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__(335); -var _realtime=__webpack_require__(337); +var _signals=__webpack_require__(336); +var _realtime=__webpack_require__(338); @@ -33569,14 +33592,14 @@ const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumb //# sourceMappingURL=main.js.map /***/ }), -/* 335 */ +/* 336 */ /***/ (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__(336); -var _realtime=__webpack_require__(337); +var _core=__webpack_require__(337); +var _realtime=__webpack_require__(338); @@ -33610,7 +33633,7 @@ return{name,number,description,supported,action,forced,standard}; //# sourceMappingURL=signals.js.map /***/ }), -/* 336 */ +/* 337 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33889,7 +33912,7 @@ standard:"other"}];exports.SIGNALS=SIGNALS; //# sourceMappingURL=core.js.map /***/ }), -/* 337 */ +/* 338 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33914,7 +33937,7 @@ const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; //# sourceMappingURL=realtime.js.map /***/ }), -/* 338 */ +/* 339 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -33973,7 +33996,7 @@ module.exports.node = opts => { /***/ }), -/* 339 */ +/* 340 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34092,14 +34115,14 @@ module.exports = { /***/ }), -/* 340 */ +/* 341 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const isStream = __webpack_require__(341); -const getStream = __webpack_require__(342); -const mergeStream = __webpack_require__(346); +const isStream = __webpack_require__(342); +const getStream = __webpack_require__(343); +const mergeStream = __webpack_require__(347); // `input` option const handleInput = (spawned, input) => { @@ -34196,7 +34219,7 @@ module.exports = { /***/ }), -/* 341 */ +/* 342 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34232,13 +34255,13 @@ module.exports = isStream; /***/ }), -/* 342 */ +/* 343 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pump = __webpack_require__(343); -const bufferStream = __webpack_require__(345); +const pump = __webpack_require__(344); +const bufferStream = __webpack_require__(346); class MaxBufferError extends Error { constructor() { @@ -34297,11 +34320,11 @@ module.exports.MaxBufferError = MaxBufferError; /***/ }), -/* 343 */ +/* 344 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(162) -var eos = __webpack_require__(344) +var eos = __webpack_require__(345) var fs = __webpack_require__(134) // we only need fs to get the ReadStream and WriteStream prototypes var noop = function () {} @@ -34385,7 +34408,7 @@ module.exports = pump /***/ }), -/* 344 */ +/* 345 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(162); @@ -34485,7 +34508,7 @@ module.exports = eos; /***/ }), -/* 345 */ +/* 346 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34544,7 +34567,7 @@ module.exports = options => { /***/ }), -/* 346 */ +/* 347 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34592,7 +34615,7 @@ module.exports = function (/*streams...*/) { /***/ }), -/* 347 */ +/* 348 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34645,7 +34668,7 @@ module.exports = { /***/ }), -/* 348 */ +/* 349 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34690,7 +34713,7 @@ module.exports = { /***/ }), -/* 349 */ +/* 350 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -34698,12 +34721,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__(350); -module.exports.cli = __webpack_require__(354); +module.exports = __webpack_require__(351); +module.exports.cli = __webpack_require__(355); /***/ }), -/* 350 */ +/* 351 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -34718,9 +34741,9 @@ var stream = __webpack_require__(138); var util = __webpack_require__(112); var fs = __webpack_require__(134); -var through = __webpack_require__(351); -var duplexer = __webpack_require__(352); -var StringDecoder = __webpack_require__(353).StringDecoder; +var through = __webpack_require__(352); +var duplexer = __webpack_require__(353); +var StringDecoder = __webpack_require__(354).StringDecoder; module.exports = Logger; @@ -34909,7 +34932,7 @@ function lineMerger(host) { /***/ }), -/* 351 */ +/* 352 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -35023,7 +35046,7 @@ function through (write, end, opts) { /***/ }), -/* 352 */ +/* 353 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -35116,13 +35139,13 @@ function duplex(writer, reader) { /***/ }), -/* 353 */ +/* 354 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 354 */ +/* 355 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -35133,11 +35156,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(355); +var minimist = __webpack_require__(356); var path = __webpack_require__(4); -var Logger = __webpack_require__(350); -var pkg = __webpack_require__(356); +var Logger = __webpack_require__(351); +var pkg = __webpack_require__(357); module.exports = cli; @@ -35191,7 +35214,7 @@ function usage($0, p) { /***/ }), -/* 355 */ +/* 356 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -35442,13 +35465,13 @@ function isNumber (x) { /***/ }), -/* 356 */ +/* 357 */ /***/ (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\"}}"); /***/ }), -/* 357 */ +/* 358 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -35456,13 +35479,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__(134); /* 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__(358); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(359); /* 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__(112); /* 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__(314); /* 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__(359); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(360); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -35661,20 +35684,20 @@ async function getAllChecksums(kbn, log, yarnLock) { } /***/ }), -/* 358 */ +/* 359 */ /***/ (function(module, exports) { module.exports = require("crypto"); /***/ }), -/* 359 */ +/* 360 */ /***/ (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 export (binding) */ __webpack_require__.d(__webpack_exports__, "resolveDepsForProject", function() { return resolveDepsForProject; }); -/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361); /* 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__(131); /* @@ -35787,7 +35810,7 @@ function resolveDepsForProject({ } /***/ }), -/* 360 */ +/* 361 */ /***/ (function(module, exports, __webpack_require__) { module.exports = @@ -37346,7 +37369,7 @@ module.exports = invariant; /* 9 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(358); +module.exports = __webpack_require__(359); /***/ }), /* 10 */, @@ -39670,7 +39693,7 @@ function onceStrict (fn) { /* 63 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(361); +module.exports = __webpack_require__(362); /***/ }), /* 64 */, @@ -46065,13 +46088,13 @@ module.exports = process && support(supportLevel); /******/ ]); /***/ }), -/* 361 */ +/* 362 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 362 */ +/* 363 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46168,13 +46191,13 @@ class BootstrapCacheFile { } /***/ }), -/* 363 */ +/* 364 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateDependencies", function() { return validateDependencies; }); -/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(360); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(361); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_1__); @@ -46185,7 +46208,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); -/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(364); +/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(365); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -46377,7 +46400,7 @@ function getDevOnlyProductionDepsTree(kbn, projectName) { } /***/ }), -/* 364 */ +/* 365 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46530,7 +46553,7 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 365 */ +/* 366 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -46538,7 +46561,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(366); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(367); /* 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__); @@ -46638,20 +46661,20 @@ const CleanCommand = { }; /***/ }), -/* 366 */ +/* 367 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(367); -const chalk = __webpack_require__(368); -const cliCursor = __webpack_require__(375); -const cliSpinners = __webpack_require__(379); -const logSymbols = __webpack_require__(381); -const stripAnsi = __webpack_require__(390); -const wcwidth = __webpack_require__(392); -const isInteractive = __webpack_require__(396); -const MuteStream = __webpack_require__(397); +const readline = __webpack_require__(368); +const chalk = __webpack_require__(369); +const cliCursor = __webpack_require__(376); +const cliSpinners = __webpack_require__(378); +const logSymbols = __webpack_require__(380); +const stripAnsi = __webpack_require__(389); +const wcwidth = __webpack_require__(391); +const isInteractive = __webpack_require__(395); +const MuteStream = __webpack_require__(396); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -47004,23 +47027,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 367 */ +/* 368 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 368 */ +/* 369 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(369); +const ansiStyles = __webpack_require__(370); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(373); +} = __webpack_require__(374); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -47221,7 +47244,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(374); + template = __webpack_require__(375); } return template(chalk, parts.join('')); @@ -47250,7 +47273,7 @@ module.exports = chalk; /***/ }), -/* 369 */ +/* 370 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47296,7 +47319,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(370); + colorConvert = __webpack_require__(371); } const offset = isBackground ? 10 : 0; @@ -47421,11 +47444,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 370 */ +/* 371 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(371); -const route = __webpack_require__(372); +const conversions = __webpack_require__(372); +const route = __webpack_require__(373); const convert = {}; @@ -47508,7 +47531,7 @@ module.exports = convert; /***/ }), -/* 371 */ +/* 372 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -48353,10 +48376,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 372 */ +/* 373 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(371); +const conversions = __webpack_require__(372); /* This function routes a model to all other models. @@ -48456,7 +48479,7 @@ module.exports = function (fromModel) { /***/ }), -/* 373 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48502,7 +48525,7 @@ module.exports = { /***/ }), -/* 374 */ +/* 375 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48643,12 +48666,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 375 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(376); +const restoreCursor = __webpack_require__(377); let isHidden = false; @@ -48685,12 +48708,12 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 376 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(377); +const onetime = __webpack_require__(332); const signalExit = __webpack_require__(304); module.exports = onetime(() => { @@ -48700,63 +48723,6 @@ module.exports = onetime(() => { }); -/***/ }), -/* 377 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const mimicFn = __webpack_require__(378); - -const calledFunctions = new WeakMap(); - -const oneTime = (fn, options = {}) => { - if (typeof fn !== 'function') { - throw new TypeError('Expected a function'); - } - - let ret; - let isCalled = false; - let callCount = 0; - const functionName = fn.displayName || fn.name || ''; - - const onetime = function (...args) { - calledFunctions.set(onetime, ++callCount); - - if (isCalled) { - if (options.throw === true) { - throw new Error(`Function \`${functionName}\` can only be called once`); - } - - return ret; - } - - isCalled = true; - ret = fn.apply(this, args); - fn = null; - - return ret; - }; - - mimicFn(onetime, fn); - calledFunctions.set(onetime, callCount); - - return onetime; -}; - -module.exports = oneTime; -// TODO: Remove this for the next major release -module.exports.default = oneTime; - -module.exports.callCount = fn => { - if (!calledFunctions.has(fn)) { - throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); - } - - return calledFunctions.get(fn); -}; - - /***/ }), /* 378 */ /***/ (function(module, exports, __webpack_require__) { @@ -48764,27 +48730,7 @@ module.exports.callCount = fn => { "use strict"; -const mimicFn = (to, from) => { - for (const prop of Reflect.ownKeys(from)) { - Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); - } - - return to; -}; - -module.exports = mimicFn; -// TODO: Remove this for the next major release -module.exports.default = mimicFn; - - -/***/ }), -/* 379 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const spinners = Object.assign({}, __webpack_require__(380)); +const spinners = Object.assign({}, __webpack_require__(379)); const spinnersList = Object.keys(spinners); @@ -48802,18 +48748,18 @@ module.exports.default = spinners; /***/ }), -/* 380 */ +/* 379 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]}}"); /***/ }), -/* 381 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(382); +const chalk = __webpack_require__(381); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -48835,16 +48781,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 382 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(383); -const stdoutColor = __webpack_require__(388).stdout; +const ansiStyles = __webpack_require__(382); +const stdoutColor = __webpack_require__(387).stdout; -const template = __webpack_require__(389); +const template = __webpack_require__(388); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -49070,12 +49016,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 383 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(384); +const colorConvert = __webpack_require__(383); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -49243,11 +49189,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 384 */ +/* 383 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(385); -var route = __webpack_require__(387); +var conversions = __webpack_require__(384); +var route = __webpack_require__(386); var convert = {}; @@ -49327,11 +49273,11 @@ module.exports = convert; /***/ }), -/* 385 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(386); +var cssKeywords = __webpack_require__(385); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -50201,7 +50147,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 386 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50360,10 +50306,10 @@ module.exports = { /***/ }), -/* 387 */ +/* 386 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(385); +var conversions = __webpack_require__(384); /* this function routes a model to all other models. @@ -50463,7 +50409,7 @@ module.exports = function (fromModel) { /***/ }), -/* 388 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50601,7 +50547,7 @@ module.exports = { /***/ }), -/* 389 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50736,18 +50682,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 390 */ +/* 389 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(391); +const ansiRegex = __webpack_require__(390); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 391 */ +/* 390 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50764,14 +50710,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 392 */ +/* 391 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(393) -var combining = __webpack_require__(395) +var defaults = __webpack_require__(392) +var combining = __webpack_require__(394) var DEFAULTS = { nul: 0, @@ -50870,10 +50816,10 @@ function bisearch(ucs) { /***/ }), -/* 393 */ +/* 392 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(394); +var clone = __webpack_require__(393); module.exports = function(options, defaults) { options = options || {}; @@ -50888,7 +50834,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 394 */ +/* 393 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -51060,7 +51006,7 @@ if ( true && module.exports) { /***/ }), -/* 395 */ +/* 394 */ /***/ (function(module, exports) { module.exports = [ @@ -51116,7 +51062,7 @@ module.exports = [ /***/ }), -/* 396 */ +/* 395 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51132,7 +51078,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 397 */ +/* 396 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -51283,7 +51229,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 398 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51344,7 +51290,7 @@ const RunCommand = { }; /***/ }), -/* 399 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51354,7 +51300,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(400); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(399); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51439,14 +51385,14 @@ const WatchCommand = { }; /***/ }), -/* 400 */ +/* 399 */ /***/ (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__(401); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51513,141 +51459,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 401 */ +/* 400 */ /***/ (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__(402); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(401); /* 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__(403); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(402); /* 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__(404); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403); /* 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__(405); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(404); /* 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__(406); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(405); /* 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__(407); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(406); /* 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__(408); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(407); /* 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__(409); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(408); /* 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__(410); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(409); /* 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__(411); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(410); /* 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__(412); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(411); /* 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__(413); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(412); /* 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__(414); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(413); /* 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__(415); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(414); /* 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__(416); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(415); /* 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__(417); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(416); /* 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__(418); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(417); /* 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__(419); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(418); /* 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__(421); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(420); /* 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__(422); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(421); /* 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__(423); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(422); /* 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__(424); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(423); /* 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__(425); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(424); /* 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__(426); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(425); /* 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__(429); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(428); /* 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__(430); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(429); /* 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__(431); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(430); /* 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__(432); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(431); /* 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__(433); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); /* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(434); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(433); /* 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__(435); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(434); /* 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__(436); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(435); /* 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__(437); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(436); /* 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__(438); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(437); /* 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__(439); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(438); /* 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__(440); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(439); /* 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__(442); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(441); /* 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__(443); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(442); /* 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__(444); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(443); /* 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__(447); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(446); /* 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); @@ -51658,175 +51604,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(448); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(447); /* 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__(449); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(448); /* 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__(450); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(449); /* 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__(451); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(450); /* 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__(452); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(451); /* 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__(453); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(452); /* 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__(454); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(453); /* 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__(455); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(454); /* 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__(456); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(455); /* 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__(457); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(456); /* 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__(458); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(457); /* 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__(459); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(458); /* 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__(460); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(459); /* 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__(445); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(444); /* 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__(461); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(460); /* 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__(462); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(461); /* 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__(463); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(462); /* 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__(464); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(463); /* 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__(465); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(464); /* 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__(466); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(465); /* 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__(446); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(445); /* 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__(467); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(466); /* 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__(468); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(467); /* 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__(469); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(468); /* 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__(470); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(469); /* 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__(471); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(470); /* 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__(472); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(471); /* 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__(473); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(472); /* 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__(474); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(473); /* 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__(475); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(474); /* 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__(476); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(475); /* 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__(478); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(477); /* 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__(479); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(478); /* 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__(480); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(479); /* 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__(428); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(427); /* 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__(441); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(440); /* 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__(481); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(480); /* 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__(482); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(481); /* 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__(483); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(482); /* 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__(484); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(483); /* 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__(485); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(484); /* 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__(427); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(426); /* 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__(486); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(485); /* 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__(487); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(486); /* 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__(488); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(487); /* 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__(489); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(488); /* 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__(490); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(489); /* 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__(491); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(490); /* 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__(492); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(491); /* 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__(493); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(492); /* 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__(494); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(493); /* 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__(495); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(494); /* 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__(496); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(495); /* 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__(497); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(496); /* 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__(498); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(497); /* 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 */ @@ -51937,7 +51883,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 402 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52016,14 +51962,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 403 */ +/* 402 */ /***/ (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__(402); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(401); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -52039,7 +51985,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 404 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52086,7 +52032,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 405 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52187,7 +52133,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 406 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52348,7 +52294,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 407 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52467,7 +52413,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 408 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52560,7 +52506,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 409 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52620,7 +52566,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 410 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52636,7 +52582,7 @@ function combineAll(project) { /***/ }), -/* 411 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52668,7 +52614,7 @@ function combineLatest() { /***/ }), -/* 412 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52688,7 +52634,7 @@ function concat() { /***/ }), -/* 413 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52704,13 +52650,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 414 */ +/* 413 */ /***/ (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__(413); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(412); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -52720,7 +52666,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 415 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52785,7 +52731,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 416 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52870,7 +52816,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 417 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52946,7 +52892,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 418 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52996,7 +52942,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 419 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53004,7 +52950,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__(420); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419); /* 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 */ @@ -53103,7 +53049,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 420 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53117,7 +53063,7 @@ function isDate(value) { /***/ }), -/* 421 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53263,7 +53209,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 422 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53301,7 +53247,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 423 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53377,7 +53323,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 424 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53448,13 +53394,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 425 */ +/* 424 */ /***/ (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__(424); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(423); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -53464,7 +53410,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 426 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53472,9 +53418,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); /* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(428); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -53496,7 +53442,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 427 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53562,7 +53508,7 @@ function defaultErrorFactory() { /***/ }), -/* 428 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53624,7 +53570,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 429 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53646,7 +53592,7 @@ function endWith() { /***/ }), -/* 430 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53708,7 +53654,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 431 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53762,7 +53708,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 432 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53856,7 +53802,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53968,7 +53914,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 434 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54006,7 +53952,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 435 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54078,13 +54024,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 436 */ +/* 435 */ /***/ (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__(435); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(434); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -54094,7 +54040,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 437 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54102,9 +54048,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(428); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(418); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(427); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(427); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(417); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(426); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54121,7 +54067,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 438 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54158,7 +54104,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 439 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54202,7 +54148,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 440 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54210,9 +54156,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(441); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(427); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(418); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(440); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(426); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(417); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54229,7 +54175,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 441 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54306,7 +54252,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 442 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54345,7 +54291,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 443 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54395,13 +54341,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 443 */ /***/ (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__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -54414,15 +54360,15 @@ function max(comparer) { /***/ }), -/* 445 */ +/* 444 */ /***/ (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__(446); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(441); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(418); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(445); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(440); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -54443,7 +54389,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 446 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54525,7 +54471,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 447 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54545,7 +54491,7 @@ function merge() { /***/ }), -/* 448 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54570,7 +54516,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 449 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54679,13 +54625,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 450 */ +/* 449 */ /***/ (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__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -54698,7 +54644,7 @@ function min(comparer) { /***/ }), -/* 451 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54747,7 +54693,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 452 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54837,7 +54783,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 453 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54885,7 +54831,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 454 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54908,7 +54854,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 455 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54948,14 +54894,14 @@ function plucker(props, length) { /***/ }), -/* 456 */ +/* 455 */ /***/ (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__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -54968,14 +54914,14 @@ function publish(selector) { /***/ }), -/* 457 */ +/* 456 */ /***/ (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__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -54986,14 +54932,14 @@ function publishBehavior(value) { /***/ }), -/* 458 */ +/* 457 */ /***/ (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__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -55004,14 +54950,14 @@ function publishLast() { /***/ }), -/* 459 */ +/* 458 */ /***/ (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__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -55027,7 +54973,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 460 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55054,7 +55000,7 @@ function race() { /***/ }), -/* 461 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55119,7 +55065,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 462 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55213,7 +55159,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 463 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55266,7 +55212,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 464 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55352,7 +55298,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 465 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55407,7 +55353,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 466 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55467,7 +55413,7 @@ function dispatchNotification(state) { /***/ }), -/* 467 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55590,13 +55536,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 467 */ /***/ (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__(451); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(450); /* 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 */ @@ -55613,7 +55559,7 @@ function share() { /***/ }), -/* 469 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55682,7 +55628,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 470 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55762,7 +55708,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 471 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55804,7 +55750,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 472 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55866,7 +55812,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 473 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55923,7 +55869,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 474 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55979,7 +55925,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56008,13 +55954,13 @@ function startWith() { /***/ }), -/* 476 */ +/* 475 */ /***/ (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__(477); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(476); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -56039,7 +55985,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 477 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56103,13 +56049,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 478 */ +/* 477 */ /***/ (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__(479); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -56121,7 +56067,7 @@ function switchAll() { /***/ }), -/* 479 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56209,13 +56155,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 480 */ +/* 479 */ /***/ (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__(479); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(478); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -56225,7 +56171,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 481 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56273,7 +56219,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 481 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56341,7 +56287,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 483 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56429,7 +56375,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 484 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56531,7 +56477,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 485 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56540,7 +56486,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__(484); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(483); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -56629,7 +56575,7 @@ function dispatchNext(arg) { /***/ }), -/* 486 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56637,7 +56583,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__(446); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(445); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -56673,7 +56619,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 487 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56681,7 +56627,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__(488); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(487); /* 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 */ @@ -56698,7 +56644,7 @@ function timeout(due, scheduler) { /***/ }), -/* 488 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56706,7 +56652,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__(420); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(419); /* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ @@ -56777,7 +56723,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 489 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56807,13 +56753,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 490 */ +/* 489 */ /***/ (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__(445); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(444); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -56830,7 +56776,7 @@ function toArray() { /***/ }), -/* 491 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56908,7 +56854,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 492 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56998,7 +56944,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 493 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57168,7 +57114,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 494 */ +/* 493 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57311,7 +57257,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 495 */ +/* 494 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57408,7 +57354,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 496 */ +/* 495 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57503,7 +57449,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 497 */ +/* 496 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57525,7 +57471,7 @@ function zip() { /***/ }), -/* 498 */ +/* 497 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57541,7 +57487,7 @@ function zipAll(project) { /***/ }), -/* 499 */ +/* 498 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57550,8 +57496,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(500); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(365); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(499); 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; } @@ -57633,7 +57579,7 @@ function toArray(value) { } /***/ }), -/* 500 */ +/* 499 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57641,13 +57587,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(501); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(500); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(359); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(360); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(504); 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; } @@ -57809,15 +57755,15 @@ class Kibana { } /***/ }), -/* 501 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); -const arrayUnion = __webpack_require__(502); -const arrayDiffer = __webpack_require__(503); -const arrify = __webpack_require__(504); +const arrayUnion = __webpack_require__(501); +const arrayDiffer = __webpack_require__(502); +const arrify = __webpack_require__(503); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -57841,7 +57787,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 502 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57853,7 +57799,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 503 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57868,7 +57814,7 @@ module.exports = arrayDiffer; /***/ }), -/* 504 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57898,7 +57844,7 @@ module.exports = arrify; /***/ }), -/* 505 */ +/* 504 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57968,12 +57914,12 @@ function getProjectPaths({ } /***/ }), -/* 506 */ +/* 505 */ /***/ (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__(507); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(506); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); /* @@ -57997,19 +57943,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 507 */ +/* 506 */ /***/ (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__(508); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(507); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(505); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(504); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); @@ -58146,7 +58092,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 508 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58154,14 +58100,14 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(509); -const arrify = __webpack_require__(510); -const globby = __webpack_require__(511); -const hasGlob = __webpack_require__(707); -const cpFile = __webpack_require__(709); -const junk = __webpack_require__(719); -const pFilter = __webpack_require__(720); -const CpyError = __webpack_require__(722); +const pMap = __webpack_require__(508); +const arrify = __webpack_require__(503); +const globby = __webpack_require__(509); +const hasGlob = __webpack_require__(705); +const cpFile = __webpack_require__(707); +const junk = __webpack_require__(717); +const pFilter = __webpack_require__(718); +const CpyError = __webpack_require__(720); const defaultOptions = { ignoreJunk: true @@ -58312,7 +58258,7 @@ module.exports = (source, destination, { /***/ }), -/* 509 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58400,47 +58346,17 @@ module.exports = async ( /***/ }), -/* 510 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const arrify = value => { - if (value === null || value === undefined) { - return []; - } - - if (Array.isArray(value)) { - return value; - } - - if (typeof value === 'string') { - return [value]; - } - - if (typeof value[Symbol.iterator] === 'function') { - return [...value]; - } - - return [value]; -}; - -module.exports = arrify; - - -/***/ }), -/* 511 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(512); +const arrayUnion = __webpack_require__(510); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(514); -const dirGlob = __webpack_require__(700); -const gitignore = __webpack_require__(703); +const fastGlob = __webpack_require__(512); +const dirGlob = __webpack_require__(698); +const gitignore = __webpack_require__(701); const DEFAULT_FILTER = () => false; @@ -58585,12 +58501,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 512 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(513); +var arrayUniq = __webpack_require__(511); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -58598,7 +58514,7 @@ module.exports = function () { /***/ }), -/* 513 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58667,10 +58583,10 @@ if ('Set' in global) { /***/ }), -/* 514 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(515); +const pkg = __webpack_require__(513); module.exports = pkg.async; module.exports.default = pkg.async; @@ -58683,19 +58599,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 515 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(516); -var taskManager = __webpack_require__(517); -var reader_async_1 = __webpack_require__(671); -var reader_stream_1 = __webpack_require__(695); -var reader_sync_1 = __webpack_require__(696); -var arrayUtils = __webpack_require__(698); -var streamUtils = __webpack_require__(699); +var optionsManager = __webpack_require__(514); +var taskManager = __webpack_require__(515); +var reader_async_1 = __webpack_require__(669); +var reader_stream_1 = __webpack_require__(693); +var reader_sync_1 = __webpack_require__(694); +var arrayUtils = __webpack_require__(696); +var streamUtils = __webpack_require__(697); /** * Synchronous API. */ @@ -58761,7 +58677,7 @@ function isString(source) { /***/ }), -/* 516 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58799,13 +58715,13 @@ exports.prepare = prepare; /***/ }), -/* 517 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(518); +var patternUtils = __webpack_require__(516); /** * Generate tasks based on parent directory of each pattern. */ @@ -58896,16 +58812,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 518 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(519); +var globParent = __webpack_require__(517); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(522); +var micromatch = __webpack_require__(520); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -59051,15 +58967,15 @@ exports.matchAny = matchAny; /***/ }), -/* 519 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(520); -var pathDirname = __webpack_require__(521); +var isglob = __webpack_require__(518); +var pathDirname = __webpack_require__(519); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -59082,7 +58998,7 @@ module.exports = function globParent(str) { /***/ }), -/* 520 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -59113,7 +59029,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 521 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59263,7 +59179,7 @@ module.exports.win32 = win32; /***/ }), -/* 522 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59274,18 +59190,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(523); -var toRegex = __webpack_require__(524); -var extend = __webpack_require__(637); +var braces = __webpack_require__(521); +var toRegex = __webpack_require__(522); +var extend = __webpack_require__(635); /** * Local dependencies */ -var compilers = __webpack_require__(639); -var parsers = __webpack_require__(666); -var cache = __webpack_require__(667); -var utils = __webpack_require__(668); +var compilers = __webpack_require__(637); +var parsers = __webpack_require__(664); +var cache = __webpack_require__(665); +var utils = __webpack_require__(666); var MAX_LENGTH = 1024 * 64; /** @@ -60147,7 +60063,7 @@ module.exports = micromatch; /***/ }), -/* 523 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60157,18 +60073,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(524); -var unique = __webpack_require__(546); -var extend = __webpack_require__(547); +var toRegex = __webpack_require__(522); +var unique = __webpack_require__(544); +var extend = __webpack_require__(545); /** * Local dependencies */ -var compilers = __webpack_require__(549); -var parsers = __webpack_require__(562); -var Braces = __webpack_require__(566); -var utils = __webpack_require__(550); +var compilers = __webpack_require__(547); +var parsers = __webpack_require__(560); +var Braces = __webpack_require__(564); +var utils = __webpack_require__(548); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -60472,16 +60388,16 @@ module.exports = braces; /***/ }), -/* 524 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(525); -var define = __webpack_require__(531); -var extend = __webpack_require__(539); -var not = __webpack_require__(543); +var safe = __webpack_require__(523); +var define = __webpack_require__(529); +var extend = __webpack_require__(537); +var not = __webpack_require__(541); var MAX_LENGTH = 1024 * 64; /** @@ -60634,10 +60550,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 525 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(526); +var parse = __webpack_require__(524); var types = parse.types; module.exports = function (re, opts) { @@ -60683,13 +60599,13 @@ function isRegExp (x) { /***/ }), -/* 526 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(527); -var types = __webpack_require__(528); -var sets = __webpack_require__(529); -var positions = __webpack_require__(530); +var util = __webpack_require__(525); +var types = __webpack_require__(526); +var sets = __webpack_require__(527); +var positions = __webpack_require__(528); module.exports = function(regexpStr) { @@ -60971,11 +60887,11 @@ module.exports.types = types; /***/ }), -/* 527 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); -var sets = __webpack_require__(529); +var types = __webpack_require__(526); +var sets = __webpack_require__(527); // All of these are private and only used by randexp. @@ -61088,7 +61004,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 528 */ +/* 526 */ /***/ (function(module, exports) { module.exports = { @@ -61104,10 +61020,10 @@ module.exports = { /***/ }), -/* 529 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(526); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -61192,10 +61108,10 @@ exports.anyChar = function() { /***/ }), -/* 530 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(528); +var types = __webpack_require__(526); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -61215,7 +61131,7 @@ exports.end = function() { /***/ }), -/* 531 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61228,8 +61144,8 @@ exports.end = function() { -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -61260,7 +61176,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 532 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61279,7 +61195,7 @@ module.exports = function isObject(val) { /***/ }), -/* 533 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61292,9 +61208,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(534); -var isAccessor = __webpack_require__(535); -var isData = __webpack_require__(537); +var typeOf = __webpack_require__(532); +var isAccessor = __webpack_require__(533); +var isData = __webpack_require__(535); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -61308,7 +61224,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 534 */ +/* 532 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61443,7 +61359,7 @@ function isBuffer(val) { /***/ }), -/* 535 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61456,7 +61372,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(536); +var typeOf = __webpack_require__(534); // accessor descriptor properties var accessor = { @@ -61519,7 +61435,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 536 */ +/* 534 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61654,7 +61570,7 @@ function isBuffer(val) { /***/ }), -/* 537 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61667,7 +61583,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(538); +var typeOf = __webpack_require__(536); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -61710,7 +61626,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 538 */ +/* 536 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -61845,14 +61761,14 @@ function isBuffer(val) { /***/ }), -/* 539 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(540); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(538); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -61912,7 +61828,7 @@ function isEnum(obj, key) { /***/ }), -/* 540 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61925,7 +61841,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -61933,7 +61849,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 541 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61946,7 +61862,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); function isObjectObject(o) { return isObject(o) === true @@ -61977,7 +61893,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 542 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62024,14 +61940,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 543 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(544); -var safe = __webpack_require__(525); +var extend = __webpack_require__(542); +var safe = __webpack_require__(523); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -62103,14 +62019,14 @@ module.exports = toRegex; /***/ }), -/* 544 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(545); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(543); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -62170,7 +62086,7 @@ function isEnum(obj, key) { /***/ }), -/* 545 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62183,7 +62099,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -62191,7 +62107,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 546 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62241,13 +62157,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 547 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(548); +var isObject = __webpack_require__(546); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -62281,7 +62197,7 @@ function hasOwn(obj, key) { /***/ }), -/* 548 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62301,13 +62217,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 549 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(550); +var utils = __webpack_require__(548); module.exports = function(braces, options) { braces.compiler @@ -62590,25 +62506,25 @@ function hasQueue(node) { /***/ }), -/* 550 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(551); +var splitString = __webpack_require__(549); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(547); -utils.flatten = __webpack_require__(554); -utils.isObject = __webpack_require__(532); -utils.fillRange = __webpack_require__(555); -utils.repeat = __webpack_require__(561); -utils.unique = __webpack_require__(546); +utils.extend = __webpack_require__(545); +utils.flatten = __webpack_require__(552); +utils.isObject = __webpack_require__(530); +utils.fillRange = __webpack_require__(553); +utils.repeat = __webpack_require__(559); +utils.unique = __webpack_require__(544); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -62940,7 +62856,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 551 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62953,7 +62869,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(552); +var extend = __webpack_require__(550); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -63118,14 +63034,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 552 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(553); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(551); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63185,7 +63101,7 @@ function isEnum(obj, key) { /***/ }), -/* 553 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63198,7 +63114,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63206,7 +63122,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 554 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63235,7 +63151,7 @@ function flat(arr, res) { /***/ }), -/* 555 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63249,10 +63165,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(556); -var extend = __webpack_require__(547); -var repeat = __webpack_require__(559); -var toRegex = __webpack_require__(560); +var isNumber = __webpack_require__(554); +var extend = __webpack_require__(545); +var repeat = __webpack_require__(557); +var toRegex = __webpack_require__(558); /** * Return a range of numbers or letters. @@ -63450,7 +63366,7 @@ module.exports = fillRange; /***/ }), -/* 556 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63463,7 +63379,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); module.exports = function isNumber(num) { var type = typeOf(num); @@ -63479,10 +63395,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 557 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -63601,7 +63517,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 558 */ +/* 556 */ /***/ (function(module, exports) { /*! @@ -63628,7 +63544,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 559 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63705,7 +63621,7 @@ function repeat(str, num) { /***/ }), -/* 560 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63718,8 +63634,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(559); -var isNumber = __webpack_require__(556); +var repeat = __webpack_require__(557); +var isNumber = __webpack_require__(554); var cache = {}; function toRegexRange(min, max, options) { @@ -64006,7 +63922,7 @@ module.exports = toRegexRange; /***/ }), -/* 561 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64031,14 +63947,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 562 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(563); -var utils = __webpack_require__(550); +var Node = __webpack_require__(561); +var utils = __webpack_require__(548); /** * Braces parsers @@ -64398,15 +64314,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 563 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(532); -var define = __webpack_require__(564); -var utils = __webpack_require__(565); +var isObject = __webpack_require__(530); +var define = __webpack_require__(562); +var utils = __webpack_require__(563); var ownNames; /** @@ -64897,7 +64813,7 @@ exports = module.exports = Node; /***/ }), -/* 564 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64910,7 +64826,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -64935,13 +64851,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 565 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); var utils = module.exports; /** @@ -65961,17 +65877,17 @@ function assert(val, message) { /***/ }), -/* 566 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(547); -var Snapdragon = __webpack_require__(567); -var compilers = __webpack_require__(549); -var parsers = __webpack_require__(562); -var utils = __webpack_require__(550); +var extend = __webpack_require__(545); +var Snapdragon = __webpack_require__(565); +var compilers = __webpack_require__(547); +var parsers = __webpack_require__(560); +var utils = __webpack_require__(548); /** * Customize Snapdragon parser and renderer @@ -66072,17 +65988,17 @@ module.exports = Braces; /***/ }), -/* 567 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(568); -var define = __webpack_require__(595); -var Compiler = __webpack_require__(605); -var Parser = __webpack_require__(634); -var utils = __webpack_require__(614); +var Base = __webpack_require__(566); +var define = __webpack_require__(593); +var Compiler = __webpack_require__(603); +var Parser = __webpack_require__(632); +var utils = __webpack_require__(612); var regexCache = {}; var cache = {}; @@ -66253,20 +66169,20 @@ module.exports.Parser = Parser; /***/ }), -/* 568 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(569); -var CacheBase = __webpack_require__(570); -var Emitter = __webpack_require__(571); -var isObject = __webpack_require__(532); -var merge = __webpack_require__(589); -var pascal = __webpack_require__(592); -var cu = __webpack_require__(593); +var define = __webpack_require__(567); +var CacheBase = __webpack_require__(568); +var Emitter = __webpack_require__(569); +var isObject = __webpack_require__(530); +var merge = __webpack_require__(587); +var pascal = __webpack_require__(590); +var cu = __webpack_require__(591); /** * Optionally define a custom `cache` namespace to use. @@ -66695,7 +66611,7 @@ module.exports.namespace = namespace; /***/ }), -/* 569 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66708,7 +66624,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66733,21 +66649,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 570 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(532); -var Emitter = __webpack_require__(571); -var visit = __webpack_require__(572); -var toPath = __webpack_require__(575); -var union = __webpack_require__(576); -var del = __webpack_require__(580); -var get = __webpack_require__(578); -var has = __webpack_require__(585); -var set = __webpack_require__(588); +var isObject = __webpack_require__(530); +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__(586); /** * Create a `Cache` constructor that when instantiated will @@ -67001,7 +66917,7 @@ module.exports.namespace = namespace; /***/ }), -/* 571 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { @@ -67170,7 +67086,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 572 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67183,8 +67099,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(573); -var mapVisit = __webpack_require__(574); +var visit = __webpack_require__(571); +var mapVisit = __webpack_require__(572); module.exports = function(collection, method, val) { var result; @@ -67207,7 +67123,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 573 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67220,7 +67136,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -67247,14 +67163,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 574 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(573); +var visit = __webpack_require__(571); /** * Map `visit` over an array of objects. @@ -67291,7 +67207,7 @@ function isObject(val) { /***/ }), -/* 575 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67304,7 +67220,7 @@ function isObject(val) { -var typeOf = __webpack_require__(557); +var typeOf = __webpack_require__(555); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -67331,16 +67247,16 @@ function filter(arr) { /***/ }), -/* 576 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(548); -var union = __webpack_require__(577); -var get = __webpack_require__(578); -var set = __webpack_require__(579); +var isObject = __webpack_require__(546); +var union = __webpack_require__(575); +var get = __webpack_require__(576); +var set = __webpack_require__(577); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -67368,7 +67284,7 @@ function arrayify(val) { /***/ }), -/* 577 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67404,7 +67320,7 @@ module.exports = function union(init) { /***/ }), -/* 578 */ +/* 576 */ /***/ (function(module, exports) { /*! @@ -67460,7 +67376,7 @@ function toString(val) { /***/ }), -/* 579 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67473,10 +67389,10 @@ function toString(val) { -var split = __webpack_require__(551); -var extend = __webpack_require__(547); -var isPlainObject = __webpack_require__(541); -var isObject = __webpack_require__(548); +var split = __webpack_require__(549); +var extend = __webpack_require__(545); +var isPlainObject = __webpack_require__(539); +var isObject = __webpack_require__(546); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67522,7 +67438,7 @@ function isValidKey(key) { /***/ }), -/* 580 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67535,8 +67451,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(532); -var has = __webpack_require__(581); +var isObject = __webpack_require__(530); +var has = __webpack_require__(579); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -67561,7 +67477,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 581 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67574,9 +67490,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(582); -var hasValues = __webpack_require__(584); -var get = __webpack_require__(578); +var isObject = __webpack_require__(580); +var hasValues = __webpack_require__(582); +var get = __webpack_require__(576); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -67587,7 +67503,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 582 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67600,7 +67516,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(583); +var isArray = __webpack_require__(581); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -67608,7 +67524,7 @@ module.exports = function isObject(val) { /***/ }), -/* 583 */ +/* 581 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -67619,7 +67535,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 584 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67662,7 +67578,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 585 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67675,9 +67591,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(532); -var hasValues = __webpack_require__(586); -var get = __webpack_require__(578); +var isObject = __webpack_require__(530); +var hasValues = __webpack_require__(584); +var get = __webpack_require__(576); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -67685,7 +67601,7 @@ module.exports = function(val, prop) { /***/ }), -/* 586 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67698,8 +67614,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(587); -var isNumber = __webpack_require__(556); +var typeOf = __webpack_require__(585); +var isNumber = __webpack_require__(554); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -67752,10 +67668,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 587 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -67877,7 +67793,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 588 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67890,10 +67806,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(551); -var extend = __webpack_require__(547); -var isPlainObject = __webpack_require__(541); -var isObject = __webpack_require__(548); +var split = __webpack_require__(549); +var extend = __webpack_require__(545); +var isPlainObject = __webpack_require__(539); +var isObject = __webpack_require__(546); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -67939,14 +67855,14 @@ function isValidKey(key) { /***/ }), -/* 589 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(590); -var forIn = __webpack_require__(591); +var isExtendable = __webpack_require__(588); +var forIn = __webpack_require__(589); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -68010,7 +67926,7 @@ module.exports = mixinDeep; /***/ }), -/* 590 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68023,7 +67939,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68031,7 +67947,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 591 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68054,7 +67970,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 592 */ +/* 590 */ /***/ (function(module, exports) { /*! @@ -68081,14 +67997,14 @@ module.exports = pascalcase; /***/ }), -/* 593 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(594); +var utils = __webpack_require__(592); /** * Expose class utils @@ -68453,7 +68369,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 594 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68467,10 +68383,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(577); -utils.define = __webpack_require__(595); -utils.isObj = __webpack_require__(532); -utils.staticExtend = __webpack_require__(602); +utils.union = __webpack_require__(575); +utils.define = __webpack_require__(593); +utils.isObj = __webpack_require__(530); +utils.staticExtend = __webpack_require__(600); /** @@ -68481,7 +68397,7 @@ module.exports = utils; /***/ }), -/* 595 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68494,7 +68410,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(596); +var isDescriptor = __webpack_require__(594); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68519,7 +68435,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 596 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68532,9 +68448,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(597); -var isAccessor = __webpack_require__(598); -var isData = __webpack_require__(600); +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') { @@ -68548,7 +68464,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 597 */ +/* 595 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -68701,7 +68617,7 @@ function isBuffer(val) { /***/ }), -/* 598 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68714,7 +68630,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(599); +var typeOf = __webpack_require__(597); // accessor descriptor properties var accessor = { @@ -68777,10 +68693,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 599 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -68899,7 +68815,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 600 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68912,7 +68828,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(601); +var typeOf = __webpack_require__(599); // data descriptor properties var data = { @@ -68961,10 +68877,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 601 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(558); +var isBuffer = __webpack_require__(556); var toString = Object.prototype.toString; /** @@ -69083,7 +68999,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 602 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69096,8 +69012,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(603); -var define = __webpack_require__(595); +var copy = __webpack_require__(601); +var define = __webpack_require__(593); var util = __webpack_require__(112); /** @@ -69180,15 +69096,15 @@ module.exports = extend; /***/ }), -/* 603 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(557); -var copyDescriptor = __webpack_require__(604); -var define = __webpack_require__(595); +var typeOf = __webpack_require__(555); +var copyDescriptor = __webpack_require__(602); +var define = __webpack_require__(593); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -69361,7 +69277,7 @@ module.exports.has = has; /***/ }), -/* 604 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69449,16 +69365,16 @@ function isObject(val) { /***/ }), -/* 605 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(606); -var define = __webpack_require__(595); -var debug = __webpack_require__(608)('snapdragon:compiler'); -var utils = __webpack_require__(614); +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`. @@ -69612,7 +69528,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(633); + var sourcemaps = __webpack_require__(631); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -69633,7 +69549,7 @@ module.exports = Compiler; /***/ }), -/* 606 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69646,7 +69562,7 @@ module.exports = Compiler; -var utils = __webpack_require__(607); +var utils = __webpack_require__(605); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -69761,7 +69677,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 607 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69775,8 +69691,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(595); -utils.isObject = __webpack_require__(532); +utils.define = __webpack_require__(593); +utils.isObject = __webpack_require__(530); utils.isString = function(val) { @@ -69791,7 +69707,7 @@ module.exports = utils; /***/ }), -/* 608 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69800,14 +69716,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(609); + module.exports = __webpack_require__(607); } else { - module.exports = __webpack_require__(612); + module.exports = __webpack_require__(610); } /***/ }), -/* 609 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -69816,7 +69732,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(610); +exports = module.exports = __webpack_require__(608); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -69998,7 +69914,7 @@ function localstorage() { /***/ }), -/* 610 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { @@ -70014,7 +69930,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(611); +exports.humanize = __webpack_require__(609); /** * The currently active debug mode names, and names to skip. @@ -70206,7 +70122,7 @@ function coerce(val) { /***/ }), -/* 611 */ +/* 609 */ /***/ (function(module, exports) { /** @@ -70364,7 +70280,7 @@ function plural(ms, n, name) { /***/ }), -/* 612 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -70380,7 +70296,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(610); +exports = module.exports = __webpack_require__(608); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -70559,7 +70475,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(613); + var net = __webpack_require__(611); stream = new net.Socket({ fd: fd, readable: false, @@ -70618,13 +70534,13 @@ exports.enable(load()); /***/ }), -/* 613 */ +/* 611 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 614 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70634,9 +70550,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(547); -exports.SourceMap = __webpack_require__(615); -exports.sourceMapResolve = __webpack_require__(626); +exports.extend = __webpack_require__(545); +exports.SourceMap = __webpack_require__(613); +exports.sourceMapResolve = __webpack_require__(624); /** * Convert backslash in the given string to forward slashes @@ -70679,7 +70595,7 @@ exports.last = function(arr, n) { /***/ }), -/* 615 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -70687,13 +70603,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__(616).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(622).SourceMapConsumer; -exports.SourceNode = __webpack_require__(625).SourceNode; +exports.SourceMapGenerator = __webpack_require__(614).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(620).SourceMapConsumer; +exports.SourceNode = __webpack_require__(623).SourceNode; /***/ }), -/* 616 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -70703,10 +70619,10 @@ exports.SourceNode = __webpack_require__(625).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(617); -var util = __webpack_require__(619); -var ArraySet = __webpack_require__(620).ArraySet; -var MappingList = __webpack_require__(621).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 @@ -71115,7 +71031,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 617 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71155,7 +71071,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(618); +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, @@ -71261,7 +71177,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 618 */ +/* 616 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71334,7 +71250,7 @@ exports.decode = function (charCode) { /***/ }), -/* 619 */ +/* 617 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71757,7 +71673,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 620 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71767,7 +71683,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); +var util = __webpack_require__(617); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -71884,7 +71800,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 621 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71894,7 +71810,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); +var util = __webpack_require__(617); /** * Determine whether mappingB is after mappingA with respect to generated @@ -71969,7 +71885,7 @@ exports.MappingList = MappingList; /***/ }), -/* 622 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71979,11 +71895,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(619); -var binarySearch = __webpack_require__(623); -var ArraySet = __webpack_require__(620).ArraySet; -var base64VLQ = __webpack_require__(617); -var quickSort = __webpack_require__(624).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; @@ -73057,7 +72973,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 623 */ +/* 621 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73174,7 +73090,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 624 */ +/* 622 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73294,7 +73210,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 625 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73304,8 +73220,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(616).SourceMapGenerator; -var util = __webpack_require__(619); +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). @@ -73713,17 +73629,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 626 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(627) -var resolveUrl = __webpack_require__(628) -var decodeUriComponent = __webpack_require__(629) -var urix = __webpack_require__(631) -var atob = __webpack_require__(632) +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) @@ -74021,7 +73937,7 @@ module.exports = { /***/ }), -/* 627 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -74084,7 +74000,7 @@ void (function(root, factory) { /***/ }), -/* 628 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74102,13 +74018,13 @@ module.exports = resolveUrl /***/ }), -/* 629 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(630) +var decodeUriComponent = __webpack_require__(628) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -74119,7 +74035,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 630 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74220,7 +74136,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 631 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -74243,7 +74159,7 @@ module.exports = urix /***/ }), -/* 632 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74257,7 +74173,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 633 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74265,8 +74181,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(595); -var utils = __webpack_require__(614); +var define = __webpack_require__(593); +var utils = __webpack_require__(612); /** * Expose `mixin()`. @@ -74409,19 +74325,19 @@ exports.comment = function(node) { /***/ }), -/* 634 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(606); +var use = __webpack_require__(604); var util = __webpack_require__(112); -var Cache = __webpack_require__(635); -var define = __webpack_require__(595); -var debug = __webpack_require__(608)('snapdragon:parser'); -var Position = __webpack_require__(636); -var utils = __webpack_require__(614); +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`. @@ -74949,7 +74865,7 @@ module.exports = Parser; /***/ }), -/* 635 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75056,13 +74972,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 636 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(595); +var define = __webpack_require__(593); /** * Store position for a node @@ -75077,14 +74993,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 637 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(638); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(636); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -75144,7 +75060,7 @@ function isEnum(obj, key) { /***/ }), -/* 638 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75157,7 +75073,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -75165,14 +75081,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 639 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(640); -var extglob = __webpack_require__(655); +var nanomatch = __webpack_require__(638); +var extglob = __webpack_require__(653); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -75249,7 +75165,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 640 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75260,17 +75176,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(524); -var extend = __webpack_require__(641); +var toRegex = __webpack_require__(522); +var extend = __webpack_require__(639); /** * Local dependencies */ -var compilers = __webpack_require__(643); -var parsers = __webpack_require__(644); -var cache = __webpack_require__(647); -var utils = __webpack_require__(649); +var compilers = __webpack_require__(641); +var parsers = __webpack_require__(642); +var cache = __webpack_require__(645); +var utils = __webpack_require__(647); var MAX_LENGTH = 1024 * 64; /** @@ -76094,14 +76010,14 @@ module.exports = nanomatch; /***/ }), -/* 641 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(642); -var assignSymbols = __webpack_require__(542); +var isExtendable = __webpack_require__(640); +var assignSymbols = __webpack_require__(540); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76161,7 +76077,7 @@ function isEnum(obj, key) { /***/ }), -/* 642 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76174,7 +76090,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(541); +var isPlainObject = __webpack_require__(539); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76182,7 +76098,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 643 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76528,15 +76444,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 644 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(543); -var toRegex = __webpack_require__(524); -var isOdd = __webpack_require__(645); +var regexNot = __webpack_require__(541); +var toRegex = __webpack_require__(522); +var isOdd = __webpack_require__(643); /** * Characters to use in negation regex (we want to "not" match @@ -76922,7 +76838,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 645 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76935,7 +76851,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(646); +var isNumber = __webpack_require__(644); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -76949,7 +76865,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 646 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76977,14 +76893,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 647 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(648))(); +module.exports = new (__webpack_require__(646))(); /***/ }), -/* 648 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76997,7 +76913,7 @@ module.exports = new (__webpack_require__(648))(); -var MapCache = __webpack_require__(635); +var MapCache = __webpack_require__(633); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -77119,7 +77035,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 649 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77132,14 +77048,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(650)(); -var Snapdragon = __webpack_require__(567); -utils.define = __webpack_require__(651); -utils.diff = __webpack_require__(652); -utils.extend = __webpack_require__(641); -utils.pick = __webpack_require__(653); -utils.typeOf = __webpack_require__(654); -utils.unique = __webpack_require__(546); +var isWindows = __webpack_require__(648)(); +var Snapdragon = __webpack_require__(565); +utils.define = __webpack_require__(649); +utils.diff = __webpack_require__(650); +utils.extend = __webpack_require__(639); +utils.pick = __webpack_require__(651); +utils.typeOf = __webpack_require__(652); +utils.unique = __webpack_require__(544); /** * Returns true if the given value is effectively an empty string @@ -77505,7 +77421,7 @@ utils.unixify = function(options) { /***/ }), -/* 650 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -77533,7 +77449,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 651 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77546,8 +77462,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -77578,7 +77494,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 652 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77632,7 +77548,7 @@ function diffArray(one, two) { /***/ }), -/* 653 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77645,7 +77561,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(532); +var isObject = __webpack_require__(530); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -77674,7 +77590,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 654 */ +/* 652 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -77809,7 +77725,7 @@ function isBuffer(val) { /***/ }), -/* 655 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77819,18 +77735,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(547); -var unique = __webpack_require__(546); -var toRegex = __webpack_require__(524); +var extend = __webpack_require__(545); +var unique = __webpack_require__(544); +var toRegex = __webpack_require__(522); /** * Local dependencies */ -var compilers = __webpack_require__(656); -var parsers = __webpack_require__(662); -var Extglob = __webpack_require__(665); -var utils = __webpack_require__(664); +var compilers = __webpack_require__(654); +var parsers = __webpack_require__(660); +var Extglob = __webpack_require__(663); +var utils = __webpack_require__(662); var MAX_LENGTH = 1024 * 64; /** @@ -78147,13 +78063,13 @@ module.exports = extglob; /***/ }), -/* 656 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(657); +var brackets = __webpack_require__(655); /** * Extglob compilers @@ -78323,7 +78239,7 @@ module.exports = function(extglob) { /***/ }), -/* 657 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78333,17 +78249,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(658); -var parsers = __webpack_require__(660); +var compilers = __webpack_require__(656); +var parsers = __webpack_require__(658); /** * Module dependencies */ -var debug = __webpack_require__(608)('expand-brackets'); -var extend = __webpack_require__(547); -var Snapdragon = __webpack_require__(567); -var toRegex = __webpack_require__(524); +var debug = __webpack_require__(606)('expand-brackets'); +var extend = __webpack_require__(545); +var Snapdragon = __webpack_require__(565); +var toRegex = __webpack_require__(522); /** * Parses the given POSIX character class `pattern` and returns a @@ -78541,13 +78457,13 @@ module.exports = brackets; /***/ }), -/* 658 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(659); +var posix = __webpack_require__(657); module.exports = function(brackets) { brackets.compiler @@ -78635,7 +78551,7 @@ module.exports = function(brackets) { /***/ }), -/* 659 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78664,14 +78580,14 @@ module.exports = { /***/ }), -/* 660 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(661); -var define = __webpack_require__(595); +var utils = __webpack_require__(659); +var define = __webpack_require__(593); /** * Text regex @@ -78890,14 +78806,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 661 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(524); -var regexNot = __webpack_require__(543); +var toRegex = __webpack_require__(522); +var regexNot = __webpack_require__(541); var cached; /** @@ -78931,15 +78847,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 662 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(657); -var define = __webpack_require__(663); -var utils = __webpack_require__(664); +var brackets = __webpack_require__(655); +var define = __webpack_require__(661); +var utils = __webpack_require__(662); /** * Characters to use in text regex (we want to "not" match @@ -79094,7 +79010,7 @@ module.exports = parsers; /***/ }), -/* 663 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79107,7 +79023,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(531); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -79132,14 +79048,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 664 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(543); -var Cache = __webpack_require__(648); +var regex = __webpack_require__(541); +var Cache = __webpack_require__(646); /** * Utils @@ -79208,7 +79124,7 @@ utils.createRegex = function(str) { /***/ }), -/* 665 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79218,16 +79134,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(567); -var define = __webpack_require__(663); -var extend = __webpack_require__(547); +var Snapdragon = __webpack_require__(565); +var define = __webpack_require__(661); +var extend = __webpack_require__(545); /** * Local dependencies */ -var compilers = __webpack_require__(656); -var parsers = __webpack_require__(662); +var compilers = __webpack_require__(654); +var parsers = __webpack_require__(660); /** * Customize Snapdragon parser and renderer @@ -79293,16 +79209,16 @@ module.exports = Extglob; /***/ }), -/* 666 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(655); -var nanomatch = __webpack_require__(640); -var regexNot = __webpack_require__(543); -var toRegex = __webpack_require__(524); +var extglob = __webpack_require__(653); +var nanomatch = __webpack_require__(638); +var regexNot = __webpack_require__(541); +var toRegex = __webpack_require__(522); var not; /** @@ -79383,14 +79299,14 @@ function textRegex(pattern) { /***/ }), -/* 667 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(648))(); +module.exports = new (__webpack_require__(646))(); /***/ }), -/* 668 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79403,13 +79319,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(567); -utils.define = __webpack_require__(669); -utils.diff = __webpack_require__(652); -utils.extend = __webpack_require__(637); -utils.pick = __webpack_require__(653); -utils.typeOf = __webpack_require__(670); -utils.unique = __webpack_require__(546); +var Snapdragon = __webpack_require__(565); +utils.define = __webpack_require__(667); +utils.diff = __webpack_require__(650); +utils.extend = __webpack_require__(635); +utils.pick = __webpack_require__(651); +utils.typeOf = __webpack_require__(668); +utils.unique = __webpack_require__(544); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -79706,7 +79622,7 @@ utils.unixify = function(options) { /***/ }), -/* 669 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79719,8 +79635,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(532); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(530); +var isDescriptor = __webpack_require__(531); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -79751,7 +79667,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 670 */ +/* 668 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -79886,7 +79802,7 @@ function isBuffer(val) { /***/ }), -/* 671 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79905,9 +79821,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_stream_1 = __webpack_require__(689); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_stream_1 = __webpack_require__(687); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -79968,15 +79884,15 @@ exports.default = ReaderAsync; /***/ }), -/* 672 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(673); -const readdirAsync = __webpack_require__(681); -const readdirStream = __webpack_require__(684); +const readdirSync = __webpack_require__(671); +const readdirAsync = __webpack_require__(679); +const readdirStream = __webpack_require__(682); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -80060,7 +79976,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 673 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80068,11 +79984,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(674); +const DirectoryReader = __webpack_require__(672); let syncFacade = { - fs: __webpack_require__(679), - forEach: __webpack_require__(680), + fs: __webpack_require__(677), + forEach: __webpack_require__(678), sync: true }; @@ -80101,7 +80017,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 674 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80110,9 +80026,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(675); -const stat = __webpack_require__(677); -const call = __webpack_require__(678); +const normalizeOptions = __webpack_require__(673); +const stat = __webpack_require__(675); +const call = __webpack_require__(676); /** * Asynchronously reads the contents of a directory and streams the results @@ -80488,14 +80404,14 @@ module.exports = DirectoryReader; /***/ }), -/* 675 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(676); +const globToRegExp = __webpack_require__(674); module.exports = normalizeOptions; @@ -80672,7 +80588,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 676 */ +/* 674 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -80809,13 +80725,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 677 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(678); +const call = __webpack_require__(676); module.exports = stat; @@ -80890,7 +80806,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 678 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80951,14 +80867,14 @@ function callOnce (fn) { /***/ }), -/* 679 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(678); +const call = __webpack_require__(676); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -81022,7 +80938,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 680 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81051,7 +80967,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 681 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81059,12 +80975,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(682); -const DirectoryReader = __webpack_require__(674); +const maybe = __webpack_require__(680); +const DirectoryReader = __webpack_require__(672); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(683), + forEach: __webpack_require__(681), async: true }; @@ -81106,7 +81022,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 682 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81133,7 +81049,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 683 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81169,7 +81085,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 684 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81177,11 +81093,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(674); +const DirectoryReader = __webpack_require__(672); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(683), + forEach: __webpack_require__(681), async: true }; @@ -81201,16 +81117,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 685 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(686); -var entry_1 = __webpack_require__(688); -var pathUtil = __webpack_require__(687); +var deep_1 = __webpack_require__(684); +var entry_1 = __webpack_require__(686); +var pathUtil = __webpack_require__(685); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -81276,14 +81192,14 @@ exports.default = Reader; /***/ }), -/* 686 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(687); -var patternUtils = __webpack_require__(518); +var pathUtils = __webpack_require__(685); +var patternUtils = __webpack_require__(516); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -81366,7 +81282,7 @@ exports.default = DeepFilter; /***/ }), -/* 687 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81397,14 +81313,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 688 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(687); -var patternUtils = __webpack_require__(518); +var pathUtils = __webpack_require__(685); +var patternUtils = __webpack_require__(516); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -81489,7 +81405,7 @@ exports.default = EntryFilter; /***/ }), -/* 689 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81509,8 +81425,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(690); -var fs_1 = __webpack_require__(694); +var fsStat = __webpack_require__(688); +var fs_1 = __webpack_require__(692); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -81560,14 +81476,14 @@ exports.default = FileSystemStream; /***/ }), -/* 690 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(691); -const statProvider = __webpack_require__(693); +const optionsManager = __webpack_require__(689); +const statProvider = __webpack_require__(691); /** * Asynchronous API. */ @@ -81598,13 +81514,13 @@ exports.statSync = statSync; /***/ }), -/* 691 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(692); +const fsAdapter = __webpack_require__(690); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -81617,7 +81533,7 @@ exports.prepare = prepare; /***/ }), -/* 692 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81640,7 +81556,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 693 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81692,7 +81608,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 694 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81723,7 +81639,7 @@ exports.default = FileSystem; /***/ }), -/* 695 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81743,9 +81659,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_stream_1 = __webpack_require__(689); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_stream_1 = __webpack_require__(687); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -81813,7 +81729,7 @@ exports.default = ReaderStream; /***/ }), -/* 696 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81832,9 +81748,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(672); -var reader_1 = __webpack_require__(685); -var fs_sync_1 = __webpack_require__(697); +var readdir = __webpack_require__(670); +var reader_1 = __webpack_require__(683); +var fs_sync_1 = __webpack_require__(695); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -81894,7 +81810,7 @@ exports.default = ReaderSync; /***/ }), -/* 697 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81913,8 +81829,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(690); -var fs_1 = __webpack_require__(694); +var fsStat = __webpack_require__(688); +var fs_1 = __webpack_require__(692); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -81960,7 +81876,7 @@ exports.default = FileSystemSync; /***/ }), -/* 698 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81976,7 +81892,7 @@ exports.flatten = flatten; /***/ }), -/* 699 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81997,13 +81913,13 @@ exports.merge = merge; /***/ }), -/* 700 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(701); +const pathType = __webpack_require__(699); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -82069,13 +81985,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 701 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(702); +const pify = __webpack_require__(700); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -82118,7 +82034,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 702 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82209,17 +82125,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 703 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(514); -const gitIgnore = __webpack_require__(704); -const pify = __webpack_require__(705); -const slash = __webpack_require__(706); +const fastGlob = __webpack_require__(512); +const gitIgnore = __webpack_require__(702); +const pify = __webpack_require__(703); +const slash = __webpack_require__(704); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -82317,7 +82233,7 @@ module.exports.sync = options => { /***/ }), -/* 704 */ +/* 702 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -82786,7 +82702,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 705 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82861,7 +82777,7 @@ module.exports = (input, options) => { /***/ }), -/* 706 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82879,7 +82795,7 @@ module.exports = input => { /***/ }), -/* 707 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82892,7 +82808,7 @@ module.exports = input => { -var isGlob = __webpack_require__(708); +var isGlob = __webpack_require__(706); module.exports = function hasGlob(val) { if (val == null) return false; @@ -82912,7 +82828,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 708 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -82943,17 +82859,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 709 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(710); -const CpFileError = __webpack_require__(713); -const fs = __webpack_require__(715); -const ProgressEmitter = __webpack_require__(718); +const pEvent = __webpack_require__(708); +const CpFileError = __webpack_require__(711); +const fs = __webpack_require__(713); +const ProgressEmitter = __webpack_require__(716); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -83067,12 +82983,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 710 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(711); +const pTimeout = __webpack_require__(709); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -83363,12 +83279,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 711 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(712); +const pFinally = __webpack_require__(710); class TimeoutError extends Error { constructor(message) { @@ -83414,7 +83330,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 712 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83436,12 +83352,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 713 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(714); +const NestedError = __webpack_require__(712); class CpFileError extends NestedError { constructor(message, nested) { @@ -83455,7 +83371,7 @@ module.exports = CpFileError; /***/ }), -/* 714 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -83511,16 +83427,16 @@ module.exports = NestedError; /***/ }), -/* 715 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(716); -const pEvent = __webpack_require__(710); -const CpFileError = __webpack_require__(713); +const makeDir = __webpack_require__(714); +const pEvent = __webpack_require__(708); +const CpFileError = __webpack_require__(711); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -83617,7 +83533,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 716 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83625,7 +83541,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(717); +const semver = __webpack_require__(715); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -83780,7 +83696,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 717 */ +/* 715 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -85382,7 +85298,7 @@ function coerce (version, options) { /***/ }), -/* 718 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85423,7 +85339,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 719 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85469,12 +85385,12 @@ exports.default = module.exports; /***/ }), -/* 720 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(721); +const pMap = __webpack_require__(719); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -85491,7 +85407,7 @@ module.exports.default = pFilter; /***/ }), -/* 721 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85570,12 +85486,12 @@ module.exports.default = pMap; /***/ }), -/* 722 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(714); +const NestedError = __webpack_require__(712); class CpyError extends NestedError { constructor(message, nested) { diff --git a/packages/kbn-ui-framework/generator-kui/app/documentation.js b/packages/kbn-ui-framework/generator-kui/app/documentation.js deleted file mode 100644 index 3cbc0263789c65..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/app/documentation.js +++ /dev/null @@ -1,56 +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. - */ - -const Generator = require('yeoman-generator'); - -const documentationGenerator = require.resolve('../documentation/index.js'); - -module.exports = class extends Generator { - prompting() { - return this.prompt([ - { - message: 'What do you want to create?', - name: 'fileType', - type: 'list', - choices: [ - { - name: 'Page', - value: 'documentation', - }, - { - name: 'Page demo', - value: 'demo', - }, - { - name: 'Sandbox', - value: 'sandbox', - }, - ], - }, - ]).then((answers) => { - this.config = answers; - }); - } - - writing() { - this.composeWith(documentationGenerator, { - fileType: this.config.fileType, - }); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/index.js b/packages/kbn-ui-framework/generator-kui/component/index.js deleted file mode 100644 index 56c49fe6fa4717..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/index.js +++ /dev/null @@ -1,156 +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. - */ - -const chalk = require('chalk'); -const { resolve } = require('path'); -const Generator = require('yeoman-generator'); -const utils = require('../utils'); - -module.exports = class extends Generator { - constructor(args, options) { - super(args, options); - - this.fileType = options.fileType; - } - - prompting() { - return this.prompt([ - { - message: "What's the name of this component? Use snake_case, please.", - name: 'name', - type: 'input', - }, - { - message: `Where do you want to create this component's files?`, - type: 'input', - name: 'path', - default: resolve(__dirname, '../../src/components'), - store: true, - }, - { - message: 'Does it need its own directory?', - name: 'shouldMakeDirectory', - type: 'confirm', - default: true, - }, - ]).then((answers) => { - this.config = answers; - - if (!answers.name || !answers.name.trim()) { - this.log.error('Sorry, please run this generator again and provide a component name.'); - process.exit(1); - } - }); - } - - writing() { - const config = this.config; - - const writeComponent = (isStatelessFunction) => { - const componentName = utils.makeComponentName(config.name); - const cssClassName = utils.lowerCaseFirstLetter(componentName); - const fileName = config.name; - - const path = utils.addDirectoryToPath(config.path, fileName, config.shouldMakeDirectory); - - const vars = (config.vars = { - componentName, - cssClassName, - fileName, - }); - - const componentPath = (config.componentPath = `${path}/${fileName}.js`); - const testPath = (config.testPath = `${path}/${fileName}.test.js`); - const stylesPath = (config.stylesPath = `${path}/_${fileName}.scss`); - config.stylesImportPath = `./_${fileName}.scss`; - - // If it needs its own directory then it will need a root index file too. - if (this.config.shouldMakeDirectory) { - this.fs.copyTpl( - this.templatePath('_index.scss'), - this.destinationPath(`${path}/_index.scss`), - vars - ); - - this.fs.copyTpl( - this.templatePath('index.js'), - this.destinationPath(`${path}/index.js`), - vars - ); - } - - // Create component file. - this.fs.copyTpl( - isStatelessFunction - ? this.templatePath('stateless_function.js') - : this.templatePath('component.js'), - this.destinationPath(componentPath), - vars - ); - - // Create component test file. - this.fs.copyTpl(this.templatePath('test.js'), this.destinationPath(testPath), vars); - - // Create component styles file. - this.fs.copyTpl(this.templatePath('_component.scss'), this.destinationPath(stylesPath), vars); - }; - - switch (this.fileType) { - case 'component': - writeComponent(); - break; - - case 'function': - writeComponent(true); - break; - } - } - - end() { - const showImportComponentSnippet = () => { - const componentName = this.config.vars.componentName; - - this.log(chalk.white(`\n// Export component (e.. from component's index.js).`)); - this.log( - `${chalk.magenta('export')} {\n` + - ` ${componentName},\n` + - `} ${chalk.magenta('from')} ${chalk.cyan(`'./${this.config.name}'`)};` - ); - - this.log(chalk.white('\n// Import styles.')); - this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}'`)};`); - - this.log(chalk.white('\n// Import component styles into the root index.scss.')); - this.log(`${chalk.magenta('@import')} ${chalk.cyan(`'${this.config.name}/index'`)};`); - }; - - this.log('------------------------------------------------'); - this.log(chalk.bold('Handy snippets:')); - switch (this.fileType) { - case 'component': - showImportComponentSnippet(); - break; - - case 'function': - showImportComponentSnippet(); - break; - } - this.log('------------------------------------------------'); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss deleted file mode 100644 index 668cabce61327d..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/_component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.<%= cssClassName %> { - -} diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss b/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss deleted file mode 100644 index 088dee98749468..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import '<%= fileName %>'; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/component.js b/packages/kbn-ui-framework/generator-kui/component/templates/component.js deleted file mode 100644 index 31e362222471ad..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/component.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { - Component, -} from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -export class <%= componentName %> extends Component { - static propTypes = { - children: PropTypes.node, - className: PropTypes.string, - } - - constructor(props) { - super(props); - } - - render() { - const { - children, - className, - ...rest - } = this.props; - - const classes = classNames('<%= cssClassName %>', className); - - return ( -
- {children} -
- ); - } -} diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/index.js b/packages/kbn-ui-framework/generator-kui/component/templates/index.js deleted file mode 100644 index 1da6deaa79d9ae..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { - <%= componentName %>, -} from './<%= fileName %>'; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js b/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js deleted file mode 100644 index 7fcbf0c19d728e..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/stateless_function.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; - -export const <%= componentName %> = ({ - children, - className, - ...rest -}) => { - const classes = classNames('<%= cssClassName %>', className); - - return ( -
- {children} -
- ); -}; - -<%= componentName %>.propTypes = { - children: PropTypes.node, - className: PropTypes.string, -}; diff --git a/packages/kbn-ui-framework/generator-kui/component/templates/test.js b/packages/kbn-ui-framework/generator-kui/component/templates/test.js deleted file mode 100644 index 4f384d6c2d3aa0..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/component/templates/test.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { render } from 'enzyme'; -import { requiredProps } from '../../test/required_props'; - -import { <%= componentName %> } from './<%= fileName %>'; - -describe('<%= componentName %>', () => { - test('is rendered', () => { - const component = render( - <<%= componentName %> {...requiredProps} /> - ); - - expect(component) - .toMatchSnapshot(); - }); -}); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/index.js b/packages/kbn-ui-framework/generator-kui/documentation/index.js deleted file mode 100644 index 03f8d5813b2515..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/index.js +++ /dev/null @@ -1,238 +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. - */ - -const chalk = require('chalk'); -const { resolve } = require('path'); -const Generator = require('yeoman-generator'); -const utils = require('../utils'); - -const DOCUMENTATION_PAGE_PATH = resolve(__dirname, '../../doc_site/src/views'); - -module.exports = class extends Generator { - constructor(args, options) { - super(args, options); - - this.fileType = options.fileType; - } - - prompting() { - const prompts = [ - { - message: "What's the name of the component you're documenting? Use snake_case, please.", - name: 'name', - type: 'input', - store: true, - }, - ]; - - if (this.fileType === 'demo') { - prompts.push({ - message: `What's the name of the directory this demo should go in? (Within ${DOCUMENTATION_PAGE_PATH}). Use snake_case, please.`, - name: 'folderName', - type: 'input', - store: true, - default: (answers) => answers.name, - }); - - prompts.push({ - message: 'What would you like to name this demo? Use snake_case, please.', - name: 'demoName', - type: 'input', - store: true, - }); - } - - return this.prompt(prompts).then((answers) => { - this.config = answers; - }); - } - - writing() { - const config = this.config; - - const writeDocumentationPage = () => { - const componentExampleName = utils.makeComponentName(config.name, false); - const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName); - const fileName = config.name; - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - componentExamplePrefix, - fileName, - }); - - const documentationPagePath = (config.documentationPagePath = `${path}/${config.name}/${config.name}_example.js`); - - this.fs.copyTpl( - this.templatePath('documentation_page.js'), - this.destinationPath(documentationPagePath), - vars - ); - }; - - const writeDocumentationPageDemo = (fileName, folderName) => { - const componentExampleName = utils.makeComponentName(fileName, false); - const componentExamplePrefix = utils.lowerCaseFirstLetter(componentExampleName); - const componentName = utils.makeComponentName(config.name); - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - componentExamplePrefix, - componentName, - fileName, - }); - - const documentationPageDemoPath = (config.documentationPageDemoPath = `${path}/${folderName}/${fileName}.js`); - - this.fs.copyTpl( - this.templatePath('documentation_page_demo.js'), - this.destinationPath(documentationPageDemoPath), - vars - ); - }; - - const writeSandbox = () => { - const fileName = config.name; - const componentExampleName = utils.makeComponentName(fileName, false); - - const path = DOCUMENTATION_PAGE_PATH; - - const vars = (config.documentationVars = { - componentExampleName, - fileName, - }); - - const sandboxPath = (config.documentationPageDemoPath = `${path}/${config.name}/${fileName}`); - - this.fs.copyTpl( - this.templatePath('documentation_sandbox.html'), - this.destinationPath(`${sandboxPath}_sandbox.html`) - ); - - this.fs.copyTpl( - this.templatePath('documentation_sandbox.js'), - this.destinationPath(`${sandboxPath}_sandbox.js`), - vars - ); - }; - - switch (this.fileType) { - case 'documentation': - writeDocumentationPage(); - writeDocumentationPageDemo(config.name, config.name); - break; - - case 'demo': - writeDocumentationPageDemo(config.demoName, config.folderName); - break; - - case 'sandbox': - writeSandbox(); - break; - } - } - - end() { - const showImportDemoSnippet = () => { - const { - componentExampleName, - componentExamplePrefix, - fileName, - } = this.config.documentationVars; - - this.log(chalk.white('\n// Import demo into example.')); - this.log( - `${chalk.magenta('import')} ${componentExampleName} from ${chalk.cyan( - `'./${fileName}'` - )};\n` + - `${chalk.magenta('const')} ${componentExamplePrefix}Source = require(${chalk.cyan( - `'!!raw-loader!./${fileName}'` - )});\n` + - `${chalk.magenta( - 'const' - )} ${componentExamplePrefix}Html = renderToHtml(${componentExampleName});` - ); - - this.log(chalk.white('\n// Render demo.')); - this.log( - `\n` + - ` \n` + - ` Description needed: how to use the ${componentExampleName} component.\n` + - ` \n` + - `\n` + - ` \n` + - ` <${componentExampleName} />\n` + - ` \n` + - `\n` - ); - }; - - const showImportRouteSnippet = (suffix, appendToRoute) => { - const { componentExampleName, fileName } = this.config.documentationVars; - - this.log(chalk.white('\n// Import example into routes.js.')); - this.log( - `${chalk.magenta('import')} ${componentExampleName}${suffix}\n` + - ` ${chalk.magenta('from')} ${chalk.cyan( - `'../../views/${fileName}/${fileName}_${suffix.toLowerCase()}'` - )};` - ); - - this.log(chalk.white('\n// Import route definition into routes.js.')); - this.log( - `{\n` + - ` name: ${chalk.cyan(`'${componentExampleName}${appendToRoute ? suffix : ''}'`)},\n` + - ` component: ${componentExampleName}${suffix},\n` + - ` hasReact: ${chalk.magenta('true')},\n` + - `}` - ); - }; - - this.log('------------------------------------------------'); - this.log(chalk.bold('Import snippets:')); - - switch (this.fileType) { - case 'documentation': - showImportRouteSnippet('Example'); - break; - - case 'demo': - showImportDemoSnippet(); - break; - - case 'sandbox': - showImportRouteSnippet('Sandbox', true); - break; - } - this.log('------------------------------------------------'); - } -}; diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js deleted file mode 100644 index df45099bb9c647..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable import/no-duplicates */ - -import React from 'react'; - -import { renderToHtml } from '../../services'; - -import { - GuideCode, - GuideDemo, - GuidePage, - GuideSection, - GuideSectionTypes, - GuideText, -} from '../../components'; - -import <%= componentExampleName %> from './<%= fileName %>'; -import <%= componentExamplePrefix %>Source from '!!raw-loader!./<%= fileName %>'; // eslint-disable-line import/default -const <%= componentExamplePrefix %>Html = renderToHtml(<%= componentExampleName %>); - -export default props => ( - - Source, - }, { - type: GuideSectionTypes.HTML, - code: <%= componentExamplePrefix %>Html, - }]} - > - - Description needed: how to use the <%= componentExampleName %> component. - - - - <<%= componentExampleName %> /> - - - -); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js deleted file mode 100644 index 645f194bb3c7b4..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_page_demo.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import { - <%= componentName %>, -} from '../../../../components'; - -export default () => ( - <<%= componentName %>> - - > -); diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html deleted file mode 100644 index 2515d47beb72f9..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.html +++ /dev/null @@ -1 +0,0 @@ -

Do whatever you want here!

diff --git a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js b/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js deleted file mode 100644 index 6dd661601b891d..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/documentation/templates/documentation_sandbox.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import { - GuideDemo, - GuideSandbox, - GuideSandboxCodeToggle, - GuideSectionTypes, -} from '../../components'; - -import html from './<%= fileName %>_sandbox.html'; - -export default props => ( - - - - - -); diff --git a/packages/kbn-ui-framework/generator-kui/utils.js b/packages/kbn-ui-framework/generator-kui/utils.js deleted file mode 100644 index 0f7b910451767e..00000000000000 --- a/packages/kbn-ui-framework/generator-kui/utils.js +++ /dev/null @@ -1,56 +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. - */ - -function makeComponentName(str, usePrefix = true) { - const words = str.split('_'); - - const componentName = words - .map(function (word) { - return upperCaseFirstLetter(word); - }) - .join(''); - - return `${usePrefix ? 'Kui' : ''}${componentName}`; -} - -function lowerCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toLowerCase() + txt.substr(1); - }); -} - -function upperCaseFirstLetter(str) { - return str.replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1); - }); -} - -function addDirectoryToPath(path, dirName, shouldMakeDirectory) { - if (shouldMakeDirectory) { - return path + '/' + dirName; - } - return path; -} - -module.exports = { - makeComponentName: makeComponentName, - lowerCaseFirstLetter: lowerCaseFirstLetter, - upperCaseFirstLetter: upperCaseFirstLetter, - addDirectoryToPath: addDirectoryToPath, -}; diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 9e3adf3f61f81b..2b66f684b0c5d3 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -5,9 +5,7 @@ "scripts": { "build": "../../node_modules/.bin/grunt prodBuild", "docSiteStart": "../../node_modules/.bin/grunt docSiteStart", - "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild", - "createComponent": "../../node_modules/.bin/yo ./generator-kui/app/component.js", - "documentComponent": "../../node_modules/.bin/yo ./generator-kui/app/documentation.js" + "docSiteBuild": "../../node_modules/.bin/grunt docSiteBuild" }, "kibana": { "build": { diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6988a3211fa12d..48187fe4653922 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -37,6 +37,7 @@ export class DocLinksService { ELASTIC_WEBSITE_URL, links: { dashboard: { + guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#trigger-picker`, urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url-drilldown.html#templating`, @@ -134,6 +135,8 @@ export class DocLinksService { visualize: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/visualize.html`, timelionDeprecation: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html#timelion-deprecation`, + lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`, + maps: `${ELASTIC_WEBSITE_URL}maps`, }, }, }); @@ -146,6 +149,7 @@ export interface DocLinksStart { readonly ELASTIC_WEBSITE_URL: string; readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index fd2d943cab9d29..781a50f849e241 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -460,6 +460,7 @@ export interface DocLinksStart { // (undocumented) readonly links: { readonly dashboard: { + readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 333f5caf725258..a8c5df8d646305 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -21,28 +21,64 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { typeRegistryMock } from '../../../saved_objects_type_registry.mock'; +import { SavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING } from '../utils'; import { getQueryParams, getClauseForReference } from './query_params'; -const registry = typeRegistryMock.create(); +const registerTypes = (registry: SavedObjectTypeRegistry) => { + registry.registerType({ + name: 'pending', + hidden: false, + namespaceType: 'single', + mappings: { + properties: { title: { type: 'text' } }, + }, + management: { + defaultSearchField: 'title', + }, + }); -const MAPPINGS = { - properties: { - pending: { properties: { title: { type: 'text' } } }, - saved: { + registry.registerType({ + name: 'saved', + hidden: false, + namespaceType: 'single', + mappings: { properties: { title: { type: 'text', fields: { raw: { type: 'keyword' } } }, obj: { properties: { key1: { type: 'text' } } }, }, }, - // mock registry returns isMultiNamespace=true for 'shared' type - shared: { properties: { name: { type: 'keyword' } } }, - // mock registry returns isNamespaceAgnostic=true for 'global' type - global: { properties: { name: { type: 'keyword' } } }, - }, + management: { + defaultSearchField: 'title', + }, + }); + + registry.registerType({ + name: 'shared', + hidden: false, + namespaceType: 'multiple', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); + + registry.registerType({ + name: 'global', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { name: { type: 'keyword' } }, + }, + management: { + defaultSearchField: 'name', + }, + }); }; -const ALL_TYPES = Object.keys(MAPPINGS.properties); + +const ALL_TYPES = ['pending', 'saved', 'shared', 'global']; // get all possible subsets (combination) of all types const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( (subsets, value) => subsets.concat(subsets.map((set) => [...set, value])), @@ -51,48 +87,53 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( .filter((x) => x.length) // exclude empty set .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it -const createTypeClause = (type: string, namespaces?: string[]) => { - if (registry.isMultiNamespace(type)) { - const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; - return { - bool: { - must: expect.arrayContaining([{ terms: { namespaces: array } }]), - must_not: [{ exists: { field: 'namespace' } }], - }, - }; - } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; - const should: any = []; - if (nonDefaultNamespaces.length > 0) { - should.push({ terms: { namespace: nonDefaultNamespaces } }); - } - if (namespaces?.includes('default')) { - should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); - } - return { - bool: { - must: [{ term: { type } }], - should: expect.arrayContaining(should), - minimum_should_match: 1, - must_not: [{ exists: { field: 'namespaces' } }], - }, - }; - } - // isNamespaceAgnostic - return { - bool: expect.objectContaining({ - must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], - }), - }; -}; - /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake */ describe('#getQueryParams', () => { - const mappings = MAPPINGS; + let registry: SavedObjectTypeRegistry; type Result = ReturnType; + beforeEach(() => { + registry = new SavedObjectTypeRegistry(); + registerTypes(registry); + }); + + const createTypeClause = (type: string, namespaces?: string[]) => { + if (registry.isMultiNamespace(type)) { + const array = [...(namespaces ?? ['default']), ALL_NAMESPACES_STRING]; + return { + bool: { + must: expect.arrayContaining([{ terms: { namespaces: array } }]), + must_not: [{ exists: { field: 'namespace' } }], + }, + }; + } else if (registry.isSingleNamespace(type)) { + const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const should: any = []; + if (nonDefaultNamespaces.length > 0) { + should.push({ terms: { namespace: nonDefaultNamespaces } }); + } + if (namespaces?.includes('default')) { + should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); + } + return { + bool: { + must: [{ term: { type } }], + should: expect.arrayContaining(should), + minimum_should_match: 1, + must_not: [{ exists: { field: 'namespaces' } }], + }, + }; + } + // isNamespaceAgnostic + return { + bool: expect.objectContaining({ + must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], + }), + }; + }; + describe('kueryNode filter clause', () => { const expectResult = (result: Result, expected: any) => { expect(result.query.bool.filter).toEqual(expect.arrayContaining([expected])); @@ -100,13 +141,13 @@ describe('#getQueryParams', () => { describe('`kueryNode` parameter', () => { it('does not include the clause when `kueryNode` is not specified', () => { - const result = getQueryParams({ mappings, registry, kueryNode: undefined }); + const result = getQueryParams({ registry, kueryNode: undefined }); expect(result.query.bool.filter).toHaveLength(1); }); it('includes the specified Kuery clause', () => { const test = (kueryNode: KueryNode) => { - const result = getQueryParams({ mappings, registry, kueryNode }); + const result = getQueryParams({ registry, kueryNode }); const expected = esKuery.toElasticsearchQuery(kueryNode); expect(result.query.bool.filter).toHaveLength(2); expectResult(result, expected); @@ -165,7 +206,6 @@ describe('#getQueryParams', () => { it('does not include the clause when `hasReference` is not specified', () => { const result = getQueryParams({ - mappings, registry, hasReference: undefined, }); @@ -176,7 +216,6 @@ describe('#getQueryParams', () => { it('creates a should clause for specified reference when operator is `OR`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -192,7 +231,6 @@ describe('#getQueryParams', () => { it('creates a must clause for specified reference when operator is `AND`', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -210,7 +248,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'OR', @@ -229,7 +266,6 @@ describe('#getQueryParams', () => { { id: 'hello', type: 'dolly' }, ]; const result = getQueryParams({ - mappings, registry, hasReference, hasReferenceOperator: 'AND', @@ -244,7 +280,6 @@ describe('#getQueryParams', () => { it('defaults to `OR` when operator is not specified', () => { const hasReference = { id: 'foo', type: 'bar' }; const result = getQueryParams({ - mappings, registry, hasReference, }); @@ -278,14 +313,13 @@ describe('#getQueryParams', () => { }; it('searches for all known types when `type` is not specified', () => { - const result = getQueryParams({ mappings, registry, type: undefined }); + const result = getQueryParams({ registry, type: undefined }); expectResult(result, ...ALL_TYPES); }); it('searches for specified type/s', () => { const test = (typeOrTypes: string | string[]) => { const result = getQueryParams({ - mappings, registry, type: typeOrTypes, }); @@ -309,18 +343,17 @@ describe('#getQueryParams', () => { const test = (namespaces?: string[]) => { for (const typeOrTypes of ALL_TYPE_SUBSETS) { - const result = getQueryParams({ mappings, registry, type: typeOrTypes, namespaces }); + const result = getQueryParams({ registry, type: typeOrTypes, namespaces }); const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; expectResult(result, ...types.map((x) => createTypeClause(x, namespaces))); } // also test with no specified type/s - const result = getQueryParams({ mappings, registry, type: undefined, namespaces }); + const result = getQueryParams({ registry, type: undefined, namespaces }); expectResult(result, ...ALL_TYPES.map((x) => createTypeClause(x, namespaces))); }; it('normalizes and deduplicates provided namespaces', () => { const result = getQueryParams({ - mappings, registry, search: '*', namespaces: ['foo', '*', 'foo', 'bar', 'default'], @@ -360,7 +393,6 @@ describe('#getQueryParams', () => { it('supersedes `type` and `namespaces` parameters', () => { const result = getQueryParams({ - mappings, registry, type: ['pending', 'saved', 'shared', 'global'], namespaces: ['foo', 'bar', 'default'], @@ -381,148 +413,266 @@ describe('#getQueryParams', () => { }); }); - describe('search clause (query.bool.must.simple_query_string)', () => { - const search = 'foo*'; + describe('search clause (query.bool)', () => { + describe('when using simple search (query.bool.must.simple_query_string)', () => { + const search = 'foo'; - const expectResult = (result: Result, sqsClause: any) => { - expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); - }; + const expectResult = (result: Result, sqsClause: any) => { + expect(result.query.bool.must).toEqual([{ simple_query_string: sqsClause }]); + }; - describe('`search` parameter', () => { - it('does not include clause when `search` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search: undefined, + describe('`search` parameter', () => { + it('does not include clause when `search` is not specified', () => { + const result = getQueryParams({ + registry, + search: undefined, + }); + expect(result.query.bool.must).toBeUndefined(); }); - expect(result.query.bool.must).toBeUndefined(); - }); - it('creates a clause with query for specified search', () => { - const result = getQueryParams({ - mappings, - registry, - search, + it('creates a clause with query for specified search', () => { + const result = getQueryParams({ + registry, + search, + }); + expectResult(result, expect.objectContaining({ query: search })); }); - expectResult(result, expect.objectContaining({ query: search })); }); - }); - describe('`searchFields` and `rootSearchFields` parameters', () => { - const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { - const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); - }; + describe('`searchFields` and `rootSearchFields` parameters', () => { + const getExpectedFields = (searchFields: string[], typeOrTypes: string | string[]) => { + const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; + return searchFields.map((x) => types.map((y) => `${y}.${x}`)).flat(); + }; - const test = ({ - searchFields, - rootSearchFields, - }: { - searchFields?: string[]; - rootSearchFields?: string[]; - }) => { - for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const test = ({ + searchFields, + rootSearchFields, + }: { + searchFields?: string[]; + rootSearchFields?: string[]; + }) => { + for (const typeOrTypes of ALL_TYPE_SUBSETS) { + const result = getQueryParams({ + registry, + type: typeOrTypes, + search, + searchFields, + rootSearchFields, + }); + let fields = rootSearchFields || []; + if (searchFields) { + fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + } + expectResult(result, expect.objectContaining({ fields })); + } + // also test with no specified type/s const result = getQueryParams({ - mappings, registry, - type: typeOrTypes, + type: undefined, search, searchFields, rootSearchFields, }); let fields = rootSearchFields || []; if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, typeOrTypes)); + fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); } expectResult(result, expect.objectContaining({ fields })); - } - // also test with no specified type/s - const result = getQueryParams({ - mappings, - registry, - type: undefined, - search, - searchFields, - rootSearchFields, + }; + + it('throws an error if a raw search field contains a "." character', () => { + expect(() => + getQueryParams({ + registry, + type: undefined, + search, + searchFields: undefined, + rootSearchFields: ['foo', 'bar.baz'], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` + ); }); - let fields = rootSearchFields || []; - if (searchFields) { - fields = fields.concat(getExpectedFields(searchFields, ALL_TYPES)); - } - expectResult(result, expect.objectContaining({ fields })); - }; - it('throws an error if a raw search field contains a "." character', () => { - expect(() => - getQueryParams({ - mappings, + it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { + const result = getQueryParams({ registry, - type: undefined, search, searchFields: undefined, - rootSearchFields: ['foo', 'bar.baz'], - }) - ).toThrowErrorMatchingInlineSnapshot( - `"rootSearchFields entry \\"bar.baz\\" is invalid: cannot contain \\".\\" character"` - ); + rootSearchFields: undefined, + }); + expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); + }); + + it('includes specified search fields for appropriate type/s', () => { + test({ searchFields: ['title'] }); + }); + + it('supports boosting', () => { + test({ searchFields: ['title^3'] }); + }); + + it('supports multiple search fields', () => { + test({ searchFields: ['title, title.raw'] }); + }); + + it('includes specified raw search fields', () => { + test({ rootSearchFields: ['_id'] }); + }); + + it('supports multiple raw search fields', () => { + test({ rootSearchFields: ['_id', 'originId'] }); + }); + + it('supports search fields and raw search fields', () => { + test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + }); }); - it('includes lenient flag and all fields when `searchFields` and `rootSearchFields` are not specified', () => { - const result = getQueryParams({ - mappings, + describe('`defaultSearchOperator` parameter', () => { + it('does not include default_operator when `defaultSearchOperator` is not specified', () => { + const result = getQueryParams({ + registry, + search, + defaultSearchOperator: undefined, + }); + expectResult( + result, + expect.not.objectContaining({ default_operator: expect.anything() }) + ); + }); + + it('includes specified default operator', () => { + const defaultSearchOperator = 'AND'; + const result = getQueryParams({ + registry, + search, + defaultSearchOperator, + }); + expectResult( + result, + expect.objectContaining({ default_operator: defaultSearchOperator }) + ); + }); + }); + }); + + describe('when using prefix search (query.bool.should)', () => { + const searchQuery = 'foo*'; + + const getQueryParamForSearch = ({ + search, + searchFields, + type, + }: { + search?: string; + searchFields?: string[]; + type?: string[]; + }) => + getQueryParams({ registry, search, - searchFields: undefined, - rootSearchFields: undefined, + searchFields, + type, }); - expectResult(result, expect.objectContaining({ lenient: true, fields: ['*'] })); - }); - it('includes specified search fields for appropriate type/s', () => { - test({ searchFields: ['title'] }); - }); + it('uses a `should` clause instead of `must`', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); - it('supports boosting', () => { - test({ searchFields: ['title^3'] }); + expect(result.query.bool.must).toBeUndefined(); + expect(result.query.bool.should).toEqual(expect.any(Array)); + expect(result.query.bool.should.length).toBeGreaterThanOrEqual(1); + expect(result.query.bool.minimum_should_match).toBe(1); }); - - it('supports multiple search fields', () => { - test({ searchFields: ['title, title.raw'] }); + it('includes the `simple_query_string` in the `should` clauses', () => { + const result = getQueryParamForSearch({ search: searchQuery, searchFields: ['title'] }); + expect(result.query.bool.should[0]).toEqual({ + simple_query_string: expect.objectContaining({ + query: searchQuery, + }), + }); }); - it('includes specified raw search fields', () => { - test({ rootSearchFields: ['_id'] }); + it('adds a should clause for each `searchFields` / `type` tuple', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title', 'desc'], + type: ['saved', 'pending'], + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'pending.title', 'saved.desc', 'pending.desc']); }); - it('supports multiple raw search fields', () => { - test({ rootSearchFields: ['_id', 'originId'] }); + it('uses all registered types when `type` is not provided', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: undefined, + }); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(5); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['pending.title', 'saved.title', 'shared.title', 'global.title']); }); - it('supports search fields and raw search fields', () => { - test({ searchFields: ['title'], rootSearchFields: ['_id'] }); + it('removes the prefix search wildcard from the query', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title'], + type: ['saved'], + }); + const shouldClauses = result.query.bool.should; + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses[0].match_phrase_prefix['saved.title'].query).toEqual('foo'); }); - }); - describe('`defaultSearchOperator` parameter', () => { - it('does not include default_operator when `defaultSearchOperator` is not specified', () => { - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator: undefined, + it("defaults to the type's default search field when `searchFields` is not specified", () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: undefined, + type: ['saved', 'global'], }); - expectResult(result, expect.not.objectContaining({ default_operator: expect.anything() })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect( + mppClauses.map((clause: any) => Object.keys(clause.match_phrase_prefix)[0]) + ).toEqual(['saved.title', 'global.name']); }); - it('includes specified default operator', () => { - const defaultSearchOperator = 'AND'; - const result = getQueryParams({ - mappings, - registry, - search, - defaultSearchOperator, + it('supports boosting', () => { + const result = getQueryParamForSearch({ + search: searchQuery, + searchFields: ['title^3', 'description'], + type: ['saved'], }); - expectResult(result, expect.objectContaining({ default_operator: defaultSearchOperator })); + const shouldClauses = result.query.bool.should; + + expect(shouldClauses.length).toBe(3); + + const mppClauses = shouldClauses.slice(1); + + expect(mppClauses.map((clause: any) => clause.match_phrase_prefix)).toEqual([ + { 'saved.title': { query: 'foo', boost: 3 } }, + { 'saved.description': { query: 'foo', boost: 1 } }, + ]); }); }); }); @@ -532,7 +682,6 @@ describe('#getQueryParams', () => { it(`throws for ${type} when namespaces is an empty array`, () => { expect(() => getQueryParams({ - mappings, registry, namespaces: [], }) diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 8d4fe13b9bede3..f73777c4f454fc 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -20,7 +20,6 @@ import { esKuery } from '../../../es_query'; type KueryNode = any; -import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; @@ -28,22 +27,17 @@ import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; * Gets the types based on the type. Uses mappings to support * null type (all types), a single type string or an array */ -function getTypes(mappings: IndexMapping, type?: string | string[]) { +function getTypes(registry: ISavedObjectTypeRegistry, type?: string | string[]) { if (!type) { - return Object.keys(getRootPropertiesObjects(mappings)); + return registry.getAllTypes().map((registeredType) => registeredType.name); } - - if (Array.isArray(type)) { - return type; - } - - return [type]; + return Array.isArray(type) ? type : [type]; } /** * Get the field params based on the types, searchFields, and rootSearchFields */ -function getFieldsForTypes( +function getSimpleQueryStringTypeFields( types: string[], searchFields: string[] = [], rootSearchFields: string[] = [] @@ -130,7 +124,6 @@ export interface HasReferenceQueryParams { export type SearchOperator = 'AND' | 'OR'; interface QueryParams { - mappings: IndexMapping; registry: ISavedObjectTypeRegistry; namespaces?: string[]; type?: string | string[]; @@ -188,11 +181,26 @@ export function getClauseForReference(reference: HasReferenceQueryParams) { }; } +// A de-duplicated set of namespaces makes for a more efficient query. +// +// Additionally, we treat the `*` namespace as the `default` namespace. +// In the Default Distribution, the `*` is automatically expanded to include all available namespaces. +// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` +// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, +// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place +// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. +// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 +const normalizeNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize + ? Array.from( + new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) + ) + : undefined; + /** * Get the "query" related keys for the search body */ export function getQueryParams({ - mappings, registry, namespaces, type, @@ -206,7 +214,7 @@ export function getQueryParams({ kueryNode, }: QueryParams) { const types = getTypes( - mappings, + registry, typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type ); @@ -214,28 +222,10 @@ export function getQueryParams({ hasReference = [hasReference]; } - // A de-duplicated set of namespaces makes for a more effecient query. - // - // Additonally, we treat the `*` namespace as the `default` namespace. - // In the Default Distribution, the `*` is automatically expanded to include all available namespaces. - // However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*` - // to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`, - // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place - // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. - // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 - const normalizeNamespaces = (namespacesToNormalize?: string[]) => - namespacesToNormalize - ? Array.from( - new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) - ) - : undefined; - const bool: any = { filter: [ ...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []), - ...(hasReference && hasReference.length - ? [getReferencesFilter(hasReference, hasReferenceOperator)] - : []), + ...(hasReference?.length ? [getReferencesFilter(hasReference, hasReferenceOperator)] : []), { bool: { should: types.map((shouldType) => { @@ -251,16 +241,133 @@ export function getQueryParams({ }; if (search) { - bool.must = [ - { - simple_query_string: { - query: search, - ...getFieldsForTypes(types, searchFields, rootSearchFields), - ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), - }, - }, - ]; + const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search); + const simpleQueryStringClause = getSimpleQueryStringClause({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, + }); + + if (useMatchPhrasePrefix) { + bool.should = [ + simpleQueryStringClause, + ...getMatchPhrasePrefixClauses({ search, searchFields, types, registry }), + ]; + bool.minimum_should_match = 1; + } else { + bool.must = [simpleQueryStringClause]; + } } return { query: { bool } }; } + +// we only want to add match_phrase_prefix clauses +// if the search is a prefix search +const shouldUseMatchPhrasePrefix = (search: string): boolean => { + return search.trim().endsWith('*'); +}; + +const getMatchPhrasePrefixClauses = ({ + search, + searchFields, + registry, + types, +}: { + search: string; + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}) => { + // need to remove the prefix search operator + const query = search.replace(/[*]$/, ''); + const mppFields = getMatchPhrasePrefixFields({ searchFields, types, registry }); + return mppFields.map(({ field, boost }) => { + return { + match_phrase_prefix: { + [field]: { + query, + boost, + }, + }, + }; + }); +}; + +interface FieldWithBoost { + field: string; + boost?: number; +} + +const getMatchPhrasePrefixFields = ({ + searchFields = [], + types, + registry, +}: { + searchFields?: string[]; + types: string[]; + registry: ISavedObjectTypeRegistry; +}): FieldWithBoost[] => { + const output: FieldWithBoost[] = []; + + searchFields = searchFields.filter((field) => field !== '*'); + let fields: string[]; + if (searchFields.length === 0) { + fields = types.reduce((typeFields, type) => { + const defaultSearchField = registry.getType(type)?.management?.defaultSearchField; + if (defaultSearchField) { + return [...typeFields, `${type}.${defaultSearchField}`]; + } + return typeFields; + }, [] as string[]); + } else { + fields = []; + for (const field of searchFields) { + fields = fields.concat(types.map((type) => `${type}.${field}`)); + } + } + + fields.forEach((rawField) => { + const [field, rawBoost] = rawField.split('^'); + let boost: number = 1; + if (rawBoost) { + try { + boost = parseInt(rawBoost, 10); + } catch (e) { + boost = 1; + } + } + if (isNaN(boost)) { + boost = 1; + } + output.push({ + field, + boost, + }); + }); + return output; +}; + +const getSimpleQueryStringClause = ({ + search, + types, + searchFields, + rootSearchFields, + defaultSearchOperator, +}: { + search: string; + types: string[]; + searchFields?: string[]; + rootSearchFields?: string[]; + defaultSearchOperator?: SearchOperator; +}) => { + return { + simple_query_string: { + query: search, + ...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields), + ...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}), + }, + }; +}; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index a9f26f71a3f2b5..3522ab9ef17363 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -76,7 +76,6 @@ describe('getSearchDsl', () => { getSearchDsl(mappings, registry, opts); expect(getQueryParams).toHaveBeenCalledTimes(1); expect(getQueryParams).toHaveBeenCalledWith({ - mappings, registry, namespaces: opts.namespaces, type: opts.type, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index d5da82e5617bea..bddecc4d7f6494 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -71,7 +71,6 @@ export function getSearchDsl( return { ...getQueryParams({ - mappings, registry, namespaces, type, diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index 09a13762d4d704..897b60e91b100b 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -94,7 +94,7 @@ export interface AggsCommonStart { */ getDateMetaByDatatableColumn: ( column: DatatableColumn - ) => Promise; + ) => Promise; createAggConfigs: ( indexPattern: IndexPattern, configStates?: CreateAggConfigParams[], diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts index e56d6227345547..8eb076f5b79065 100644 --- a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts @@ -91,6 +91,43 @@ describe('getDateMetaByDatatableColumn', () => { }); }); + it('throws if unable to resolve interval', async () => { + await expect( + getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + interval: 'auto', + }, + }, + }, + }) + ).rejects.toBeDefined(); + + await expect( + getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + }, + }, + }, + }) + ).rejects.toBeDefined(); + }); + it('returns resolved auto interval', async () => { expect( await getDateMetaByDatatableColumn(params)({ @@ -106,8 +143,8 @@ describe('getDateMetaByDatatableColumn', () => { interval: 'auto', }, appliedTimeRange: { - from: 'now-5d', - to: 'now', + from: '2020-10-05T00:00:00.000Z', + to: '2020-10-10T00:00:00.000Z', }, }, }, diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts index 1bea716c6a0490..7ed8cb79f63f47 100644 --- a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts @@ -38,11 +38,11 @@ export const getDateMetaByDatatableColumn = ({ getConfig, }: DateMetaByColumnDeps) => async ( column: DatatableColumn -): Promise => { +): Promise => { if (column.meta.source !== 'esaggs') return; if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) return; const params = column.meta.sourceParams.params as AggParamsDateHistogram; - const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange; + const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange | undefined; const tz = inferTimeZone( params, @@ -52,9 +52,11 @@ export const getDateMetaByDatatableColumn = ({ ); const interval = - params.interval === 'auto' ? calculateAutoTimeExpression(appliedTimeRange) : params.interval; + params.interval === 'auto' && appliedTimeRange + ? calculateAutoTimeExpression(appliedTimeRange) + : params.interval; - if (!interval) { + if (!interval || interval === 'auto') { throw new Error('time interval could not be determined'); } diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index dba77d398c8b67..3932484801fa81 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -267,6 +267,8 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ searchSource.setField('index', indexPattern); searchSource.setField('size', 0); + const resolvedTimeRange = input?.timeRange && calculateBounds(input.timeRange); + const response = await handleCourierRequest({ searchSource, aggs, @@ -303,7 +305,10 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ input?.timeRange && args.timeFields && args.timeFields.includes(column.aggConfig.params.field?.name) - ? { from: input.timeRange.from, to: input.timeRange.to } + ? { + from: resolvedTimeRange?.min?.toISOString(), + to: resolvedTimeRange?.max?.toISOString(), + } : undefined, ...column.aggConfig.serialize(), }, diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index e6bff703aadcd3..773d61ebe2e28a 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -1057,7 +1057,7 @@ export interface Range { // Warning: (ae-missing-release-tag) "ReactExpressionRenderer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; +export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, onData$, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; // Warning: (ae-missing-release-tag) "ReactExpressionRendererProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1072,6 +1072,8 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { // (undocumented) expression: string | ExpressionAstExpression; // (undocumented) + onData$?: (data: TData, adapters?: TInspectorAdapters) => void; + // (undocumented) onEvent?: (event: ExpressionRendererEvent) => void; // (undocumented) padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 052c2a9f6a24a1..e52d4d153882ff 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -195,6 +195,38 @@ describe('ExpressionRenderer', () => { expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0); }); + it('should call onData$ prop on every data$ observable emission in loader', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + + const newData = {}; + const inspectData = {}; + const onData$ = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$, + loading$: new Subject(), + events$: new Subject(), + update: jest.fn(), + inspect: jest.fn(() => inspectData), + }; + }); + + mount(); + + expect(onData$).toHaveBeenCalledTimes(0); + + act(() => { + dataSubject.next(newData); + }); + + expect(onData$).toHaveBeenCalledTimes(1); + expect(onData$.mock.calls[0][0]).toBe(newData); + expect(onData$.mock.calls[0][1]).toBe(inspectData); + }); + it('should fire onEvent prop on every events$ observable emission in loader', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index fecebf36ab7e6a..894325c8b65f7d 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -41,6 +41,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; + onData$?: (data: TData, adapters?: TInspectorAdapters) => void; /** * An observable which can be used to re-run the expression without destroying the component */ @@ -71,6 +72,7 @@ export const ReactExpressionRenderer = ({ renderError, expression, onEvent, + onData$, reload$, debounce, ...expressionLoaderOptions @@ -135,6 +137,13 @@ export const ReactExpressionRenderer = ({ }) ); } + if (onData$) { + subs.push( + expressionLoaderRef.current.data$.subscribe((newData) => { + onData$(newData, expressionLoaderRef.current?.inspect()); + }) + ); + } subs.push( expressionLoaderRef.current.loading$.subscribe(() => { hasHandledErrorRef.current = false; diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 782df67f5c58a4..6e33e18c1603b0 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -18,8 +18,7 @@ */ import { i18n } from '@kbn/i18n'; - -import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; +import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public'; import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; @@ -37,8 +36,9 @@ export function createInputControlVisTypeDefinition( defaultMessage: 'Controls', }), icon: 'controlsHorizontal', + group: VisGroups.TOOLS, description: i18n.translate('inputControl.register.controlsDescription', { - defaultMessage: 'Create interactive controls for easy dashboard manipulation.', + defaultMessage: 'Add dropdown menus and range sliders to your dashboard.', }), stage: 'experimental', visualization: InputControlVisController, diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md new file mode 100644 index 00000000000000..187da2497026e9 --- /dev/null +++ b/src/plugins/lens_oss/README.md @@ -0,0 +1,6 @@ +# lens_oss + +The lens_oss plugin registers the lens visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + +`visualizations.unregisterAlias('lensOss')` \ No newline at end of file diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/lens_oss/common/constants.ts new file mode 100644 index 00000000000000..ac92c9e1969936 --- /dev/null +++ b/src/plugins/lens_oss/common/constants.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export const APP_NAME = 'lens'; +export const PLUGIN_ID_OSS = 'lensOss'; +export const APP_PATH = '#/'; +export const APP_ICON = 'lensApp'; diff --git a/src/plugins/visualizations/public/wizard/type_selection/index.ts b/src/plugins/lens_oss/common/index.ts similarity index 94% rename from src/plugins/visualizations/public/wizard/type_selection/index.ts rename to src/plugins/lens_oss/common/index.ts index c4093b4dec3e83..fd1c2843d6b26a 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/index.ts +++ b/src/plugins/lens_oss/common/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { TypeSelection } from './type_selection'; +export * from './constants'; diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/lens_oss/config.ts new file mode 100644 index 00000000000000..6749bd83de39f9 --- /dev/null +++ b/src/plugins/lens_oss/config.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json new file mode 100644 index 00000000000000..3e3d3585f37fb8 --- /dev/null +++ b/src/plugins/lens_oss/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "lensOss", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "visualizations" + ], + "extraPublicDirs": ["common/constants"] +} diff --git a/src/plugins/lens_oss/public/index.ts b/src/plugins/lens_oss/public/index.ts new file mode 100644 index 00000000000000..f936052a37264b --- /dev/null +++ b/src/plugins/lens_oss/public/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { LensOSSPlugin } from './plugin'; + +export const plugin = () => new LensOSSPlugin(); diff --git a/packages/kbn-ui-framework/generator-kui/app/component.js b/src/plugins/lens_oss/public/plugin.ts similarity index 53% rename from packages/kbn-ui-framework/generator-kui/app/component.js rename to src/plugins/lens_oss/public/plugin.ts index bcb561f6fa7295..15b815dab87c87 100644 --- a/packages/kbn-ui-framework/generator-kui/app/component.js +++ b/src/plugins/lens_oss/public/plugin.ts @@ -16,37 +16,27 @@ * specific language governing permissions and limitations * under the License. */ +import { DocLinksStart, CoreSetup } from 'src/core/public'; +import { VisualizationsSetup } from '../../visualizations/public'; +import { getLensAliasConfig } from './vis_type_alias'; -const Generator = require('yeoman-generator'); +export interface LensPluginSetupDependencies { + visualizations: VisualizationsSetup; +} -const componentGenerator = require.resolve('../component/index.js'); +export interface LensPluginStartDependencies { + docLinks: DocLinksStart; +} -module.exports = class extends Generator { - prompting() { - return this.prompt([ - { - message: 'What do you want to create?', - name: 'fileType', - type: 'list', - choices: [ - { - name: 'Stateless function', - value: 'function', - }, - { - name: 'Component class', - value: 'component', - }, - ], - }, - ]).then((answers) => { - this.config = answers; +export class LensOSSPlugin { + setup( + core: CoreSetup, + { visualizations }: LensPluginSetupDependencies + ) { + core.getStartServices().then(([coreStart]) => { + visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks)); }); } - writing() { - this.composeWith(componentGenerator, { - fileType: this.config.fileType, - }); - } -}; + start() {} +} diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts new file mode 100644 index 00000000000000..230209646deb55 --- /dev/null +++ b/src/plugins/lens_oss/public/vis_type_alias.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { VisTypeAlias } from 'src/plugins/visualizations/public'; +import { DocLinksStart } from 'src/core/public'; +import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; + +export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ + aliasPath: APP_PATH, + aliasApp: APP_NAME, + name: PLUGIN_ID_OSS, + title: i18n.translate('lensOss.visTypeAlias.title', { + defaultMessage: 'Lens', + }), + description: i18n.translate('lensOss.visTypeAlias.description', { + defaultMessage: + 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.', + }), + icon: APP_ICON, + stage: 'production', + disabled: true, + note: i18n.translate('lensOss.visTypeAlias.note', { + defaultMessage: 'Recommended for most users.', + }), + promoTooltip: { + description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', { + defaultMessage: 'Try Lens for free with Elastic. Learn more.', + }), + link: `${links.visualize.lens}?blade=kibanaossvizwizard`, + }, +}); diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx b/src/plugins/lens_oss/server/index.ts similarity index 54% rename from src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx rename to src/plugins/lens_oss/server/index.ts index a9837313f29171..1a089a5382cc15 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/vis_type_icon.tsx +++ b/src/plugins/lens_oss/server/index.ts @@ -17,25 +17,16 @@ * under the License. */ -import { EuiIcon, IconType } from '@elastic/eui'; -import React from 'react'; +import { PluginConfigDescriptor } from 'kibana/server'; +import { copyFromRoot } from '@kbn/config'; +import { configSchema, ConfigSchema } from '../config'; -interface VisTypeIconProps { - icon?: IconType; - image?: string; -} - -/** - * This renders the icon for a specific visualization type. - * This currently checks the following: - * - If image is set, use that as the `src` of an image - * - Otherwise use the icon as an EuiIcon or the 'empty' icon if that's not set - */ -export const VisTypeIcon = ({ icon, image }: VisTypeIconProps) => { - return ( - - {image && } - {!image && } - - ); +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')], }; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md new file mode 100644 index 00000000000000..ed91de500fbfb0 --- /dev/null +++ b/src/plugins/maps_oss/README.md @@ -0,0 +1,6 @@ +# maps_oss + +The maps_oss plugin registers the maps visualization on OSS. +It is registered as disabled. The x-pack plugin should unregister this. + +`visualizations.unregisterAlias('mapsOss')` \ No newline at end of file diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts new file mode 100644 index 00000000000000..b4063c68840b38 --- /dev/null +++ b/src/plugins/maps_oss/common/constants.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export const APP_NAME = 'maps'; +export const PLUGIN_ID_OSS = 'mapsOss'; +export const APP_PATH = '/map'; +export const APP_ICON = 'gisApp'; diff --git a/src/plugins/maps_oss/common/index.ts b/src/plugins/maps_oss/common/index.ts new file mode 100644 index 00000000000000..fd1c2843d6b26a --- /dev/null +++ b/src/plugins/maps_oss/common/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './constants'; diff --git a/src/plugins/maps_oss/config.ts b/src/plugins/maps_oss/config.ts new file mode 100644 index 00000000000000..6749bd83de39f9 --- /dev/null +++ b/src/plugins/maps_oss/config.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json new file mode 100644 index 00000000000000..19770dcffaadd7 --- /dev/null +++ b/src/plugins/maps_oss/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "mapsOss", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "visualizations" + ], + "extraPublicDirs": ["common/constants"] +} diff --git a/src/plugins/maps_oss/public/index.ts b/src/plugins/maps_oss/public/index.ts new file mode 100644 index 00000000000000..ec18ff1fde638a --- /dev/null +++ b/src/plugins/maps_oss/public/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { MapsOSSPlugin } from './plugin'; + +export const plugin = () => new MapsOSSPlugin(); diff --git a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx b/src/plugins/maps_oss/public/plugin.ts similarity index 53% rename from src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx rename to src/plugins/maps_oss/public/plugin.ts index 8517919955f7cc..3369e0bf277065 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/vis_help_text.tsx +++ b/src/plugins/maps_oss/public/plugin.ts @@ -17,34 +17,27 @@ * under the License. */ -import React from 'react'; +import { DocLinksStart, CoreSetup } from 'src/core/public'; +import { VisualizationsSetup } from '../../visualizations/public'; +import { getMapsAliasConfig } from './vis_type_alias'; -import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +export interface MapsPluginSetupDependencies { + visualizations: VisualizationsSetup; +} -interface VisHelpTextProps { - name: string; - title: string; - description?: string; - highlightMsg?: string; +export interface MapsPluginStartDependencies { + docLinks: DocLinksStart; } -export const VisHelpText = ({ name, title, description, highlightMsg }: VisHelpTextProps) => { - return ( - - -

{title}

-
- -
- - {highlightMsg && ( -

- {highlightMsg} -

- )} -

{description}

-
-
-
- ); -}; +export class MapsOSSPlugin { + setup( + core: CoreSetup, + { visualizations }: MapsPluginSetupDependencies + ) { + core.getStartServices().then(([coreStart]) => { + visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks)); + }); + } + + start() {} +} diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts new file mode 100644 index 00000000000000..14fdc06bc539f7 --- /dev/null +++ b/src/plugins/maps_oss/public/vis_type_alias.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { VisTypeAlias } from 'src/plugins/visualizations/public'; +import { DocLinksStart } from 'src/core/public'; +import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; + +export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ + aliasPath: APP_PATH, + aliasApp: APP_NAME, + name: PLUGIN_ID_OSS, + title: i18n.translate('mapsOss.visTypeAlias.title', { + defaultMessage: 'Maps', + }), + description: i18n.translate('mapsOss.visTypeAlias.description', { + defaultMessage: 'Plot and style your geo data in a multi layer map.', + }), + icon: APP_ICON, + stage: 'production', + disabled: true, + promoTooltip: { + description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', { + defaultMessage: 'Try maps for free with Elastic. Learn more.', + }), + link: `${links.visualize.maps}?blade=kibanaossvizwizard`, + }, +}); diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts new file mode 100644 index 00000000000000..defc3a09c25383 --- /dev/null +++ b/src/plugins/maps_oss/server/index.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginConfigDescriptor } from 'kibana/server'; +import { copyFromRoot } from '@kbn/config'; +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')], +}; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/vis_type_markdown/public/markdown_vis.ts b/src/plugins/vis_type_markdown/public/markdown_vis.ts index 27ac038aee6fff..b6ec2cbd993b08 100644 --- a/src/plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/plugins/vis_type_markdown/public/markdown_vis.ts @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { MarkdownOptions } from './markdown_options'; import { SettingsOptions } from './settings_options_lazy'; import { DefaultEditorSize } from '../../vis_default_editor/public'; +import { VisGroups } from '../../visualizations/public'; import { toExpressionAst } from './to_ast'; export const markdownVisDefinition = { @@ -29,8 +30,12 @@ export const markdownVisDefinition = { title: 'Markdown', isAccessible: true, icon: 'visText', + group: VisGroups.TOOLS, + titleInWizard: i18n.translate('visTypeMarkdown.markdownTitleInWizard', { + defaultMessage: 'Text', + }), description: i18n.translate('visTypeMarkdown.markdownDescription', { - defaultMessage: 'Create a document using markdown syntax', + defaultMessage: 'Add text and images to your dashboard.', }), toExpressionAst, visConfig: { diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 4520069244527b..8973060848b411 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -18,8 +18,9 @@ */ import { TypeOf } from '@kbn/config-schema'; -import { metricsItems, panel, seriesItems } from './vis_schema'; +import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema'; export type SeriesItemsSchema = TypeOf; export type MetricsItemsSchema = TypeOf; export type PanelSchema = TypeOf; +export type VisPayload = TypeOf; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index 40f776050617e5..27f09fb574b0f3 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -273,4 +273,5 @@ export const visPayloadSchema = schema.object({ min: stringRequired, max: stringRequired, }), + sessionId: schema.maybe(schema.string()), }); diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index d6621870fef67e..682517ab1a9968 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,15 +25,16 @@ import { EditorController } from './application'; // @ts-ignore import { PANEL_TYPES } from '../common/panel_types'; import { VisEditor } from './application/components/vis_editor_lazy'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public'; export const metricsVisDefinition = { name: 'metrics', title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }), description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', { - defaultMessage: 'Build time-series using a visual pipeline interface', + defaultMessage: 'Perform advanced analysis of your time series data.', }), icon: 'visVisualBuilder', + group: VisGroups.PROMOTED, visConfig: { defaults: { id: '61ca57f0-469d-11e7-af02-69e470af7417', diff --git a/src/plugins/vis_type_timeseries/public/request_handler.js b/src/plugins/vis_type_timeseries/public/request_handler.js index e33d0e254f609f..12b7f3d417ef6a 100644 --- a/src/plugins/vis_type_timeseries/public/request_handler.js +++ b/src/plugins/vis_type_timeseries/public/request_handler.js @@ -32,7 +32,8 @@ export const metricsRequestHandler = async ({ const config = getUISettings(); const timezone = getTimezone(config); const uiStateObj = uiState.get(visParams.type, {}); - const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange); + const dataSearch = getDataStart(); + const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange); const scaledDataFormat = config.get('dateFormat:scaled'); const dateFormat = config.get('dateFormat'); @@ -53,6 +54,7 @@ export const metricsRequestHandler = async ({ panels: [visParams], state: uiStateObj, savedObjectId: savedObjectId || 'unsaved', + sessionId: dataSearch.search.session.getSessionId(), }), }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index 9710f7daf69b65..2c38e883cd69f3 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -28,6 +28,7 @@ describe('AbstractSearchStrategy', () => { beforeEach(() => { mockedFields = {}; req = { + payload: {}, pre: { indexPatternsService: { getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields), @@ -60,6 +61,9 @@ describe('AbstractSearchStrategy', () => { const responses = await abstractSearchStrategy.search( { + payload: { + sessionId: 1, + }, requestContext: { search: { search: searchFn }, }, @@ -76,7 +80,9 @@ describe('AbstractSearchStrategy', () => { }, indexType: undefined, }, - {} + { + sessionId: 1, + } ); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index eb22fcb1dd689d..b1e21edf8b588d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -23,8 +23,10 @@ import { IUiSettingsClient, SavedObjectsClientContract, } from 'kibana/server'; + import { Framework } from '../../../plugin'; import { IndexPatternsFetcher } from '../../../../../data/server'; +import { VisPayload } from '../../../../common/types'; /** * ReqFacade is a regular KibanaRequest object extended with additional service @@ -32,17 +34,17 @@ import { IndexPatternsFetcher } from '../../../../../data/server'; * * This will be replaced by standard KibanaRequest and RequestContext objects in a later version. */ -export type ReqFacade = FakeRequest & { +export interface ReqFacade extends FakeRequest { requestContext: RequestHandlerContext; framework: Framework; - payload: unknown; + payload: T; pre: { indexPatternsService?: IndexPatternsFetcher; }; getUiSettingsService: () => IUiSettingsClient; getSavedObjectsClient: () => SavedObjectsClientContract; getEsShardTimeout: () => Promise; -}; +} export class AbstractSearchStrategy { public indexType?: string; @@ -53,8 +55,10 @@ export class AbstractSearchStrategy { this.additionalParams = additionalParams; } - async search(req: ReqFacade, bodies: any[], options = {}) { + async search(req: ReqFacade, bodies: any[], options = {}) { const requests: any[] = []; + const { sessionId } = req.payload; + bodies.forEach((body) => { requests.push( req.requestContext @@ -67,6 +71,7 @@ export class AbstractSearchStrategy { indexType: this.indexType, }, { + sessionId, ...options, } ) diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index 17f35b75f00167..2211abb54aa93f 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -25,7 +25,7 @@ import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; import { getDefaultSpec } from './default_spec'; import { createInspectorAdapters } from './vega_inspector'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public'; import { toExpressionAst } from './to_ast'; import { VisParams } from './vega_fn'; import { getInfoMessage } from './components/experimental_map_vis_info'; @@ -41,10 +41,17 @@ export const createVegaTypeDefinition = ( title: 'Vega', getInfoMessage, description: i18n.translate('visTypeVega.type.vegaDescription', { - defaultMessage: 'Create custom visualizations using Vega and Vega-Lite', + defaultMessage: 'Use Vega to create new types of visualizations.', description: 'Vega and Vega-Lite are product names and should not be translated', }), + note: i18n.translate('visTypeVega.type.vegaNote', { + defaultMessage: 'Requires knowledge of Vega syntax.', + }), icon: 'visVega', + group: VisGroups.PROMOTED, + titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', { + defaultMessage: 'Custom visualization', + }), visConfig: { defaults: { spec: getDefaultSpec() } }, editorConfig: { optionsTemplate: VegaVisEditorComponent, diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts index 86e3b8793d618c..2b3c415087ee1e 100644 --- a/src/plugins/vis_type_vislib/public/gauge.ts +++ b/src/plugins/vis_type_vislib/public/gauge.ts @@ -61,8 +61,7 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = { title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', description: i18n.translate('visTypeVislib.gauge.gaugeDescription', { - defaultMessage: - "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.", + defaultMessage: 'Gauges indicate the status of a metric.', }), toExpressionAst, visConfig: { diff --git a/src/plugins/visualizations/public/index.scss b/src/plugins/visualizations/public/index.scss index 2b61535f3e7f23..0202419cea2328 100644 --- a/src/plugins/visualizations/public/index.scss +++ b/src/plugins/visualizations/public/index.scss @@ -1,3 +1,2 @@ -@import 'wizard/index'; @import 'embeddable/index'; @import 'components/index'; diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 7bd4466b23166b..d66a6f6113cad7 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -36,6 +36,7 @@ export { getSchemas as getVisSchemas } from './legacy/build_pipeline'; /** @public types */ export { VisualizationsSetup, VisualizationsStart }; +export { VisGroups } from './vis_types'; export type { VisTypeAlias, VisType, BaseVisTypeOptions, ReactVisTypeOptions } from './vis_types'; export { VisParams, SerializedVis, SerializedVisData, VisData } from './vis'; export type VisualizeEmbeddableFactoryContract = PublicContract; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index f20e87dbd3b6a8..66399352bea7d9 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -41,6 +41,8 @@ const createStartContract = (): VisualizationsStart => ({ get: jest.fn(), all: jest.fn(), getAliases: jest.fn(), + getByGroup: jest.fn(), + unRegisterAlias: jest.fn(), savedVisualizationsLoader: { get: jest.fn(), } as any, diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index c1dbe39def64c8..29e31e92b971e2 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -49,6 +49,7 @@ import { setOverlays, setSavedSearchLoader, setEmbeddable, + setDocLinks, } from './services'; import { VISUALIZE_EMBEDDABLE_TYPE, @@ -172,6 +173,7 @@ export class VisualizationsPlugin setCapabilities(core.application.capabilities); setHttp(core.http); setSavedObjects(core.savedObjects); + setDocLinks(core.docLinks); setIndexPatterns(data.indexPatterns); setSearch(data.search); setFilterManager(data.query.filterManager); diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index 0761b8862e8e36..08537b4ac30810 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -26,6 +26,7 @@ import { IUiSettingsClient, OverlayStart, SavedObjectsStart, + DocLinksStart, } from '../../../core/public'; import { TypesStart } from './vis_types'; import { createGetterSetter } from '../../../plugins/kibana_utils/public'; @@ -60,6 +61,8 @@ export const [getTypes, setTypes] = createGetterSetter('Types'); export const [getI18n, setI18n] = createGetterSetter('I18n'); +export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks'); + export const [getFilterManager, setFilterManager] = createGetterSetter( 'FilterManager' ); diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index f2933de723a393..807582723172da 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -20,7 +20,7 @@ import { defaultsDeep } from 'lodash'; import { ISchemas } from 'src/plugins/vis_default_editor/public'; import { VisParams } from '../types'; -import { VisType, VisTypeOptions } from './types'; +import { VisType, VisTypeOptions, VisGroups } from './types'; interface CommonBaseVisTypeOptions extends Pick< @@ -41,7 +41,14 @@ interface CommonBaseVisTypeOptions >, Pick< Partial>, - 'editorConfig' | 'hidden' | 'stage' | 'useCustomNoDataScreen' | 'visConfig' + | 'editorConfig' + | 'hidden' + | 'stage' + | 'useCustomNoDataScreen' + | 'visConfig' + | 'group' + | 'titleInWizard' + | 'note' > { options?: Partial['options']>; } @@ -72,10 +79,13 @@ export class BaseVisType implements VisType public readonly name; public readonly title; public readonly description; + public readonly note; public readonly getSupportedTriggers; public readonly icon; public readonly image; public readonly stage; + public readonly group; + public readonly titleInWizard; public readonly options; public readonly visualization; public readonly visConfig; @@ -98,6 +108,7 @@ export class BaseVisType implements VisType this.name = opts.name; this.description = opts.description ?? ''; + this.note = opts.note ?? ''; this.getSupportedTriggers = opts.getSupportedTriggers; this.title = opts.title; this.icon = opts.icon; @@ -108,6 +119,8 @@ export class BaseVisType implements VisType this.editorConfig = defaultsDeep({}, opts.editorConfig, { collections: {} }); this.options = defaultsDeep({}, opts.options, defaultOptions); this.stage = opts.stage ?? 'production'; + this.group = opts.group ?? VisGroups.AGGBASED; + this.titleInWizard = opts.titleInWizard ?? ''; this.hidden = opts.hidden ?? false; this.requestHandler = opts.requestHandler ?? 'courier'; this.responseHandler = opts.responseHandler ?? 'none'; diff --git a/src/plugins/visualizations/public/vis_types/index.ts b/src/plugins/visualizations/public/vis_types/index.ts index a46b257c9905c1..a02ac82c8d1223 100644 --- a/src/plugins/visualizations/public/vis_types/index.ts +++ b/src/plugins/visualizations/public/vis_types/index.ts @@ -18,6 +18,7 @@ */ export * from './types_service'; +export { VisGroups } from './types'; export type { VisType } from './types'; export type { BaseVisTypeOptions } from './base_vis_type'; export type { ReactVisTypeOptions } from './react_vis_type'; diff --git a/src/plugins/visualizations/public/vis_types/types.ts b/src/plugins/visualizations/public/vis_types/types.ts index 7206e9612f1024..ee804e5677243c 100644 --- a/src/plugins/visualizations/public/vis_types/types.ts +++ b/src/plugins/visualizations/public/vis_types/types.ts @@ -33,21 +33,63 @@ export interface VisTypeOptions { hierarchicalData: boolean; } +export enum VisGroups { + PROMOTED = 'promoted', + TOOLS = 'tools', + AGGBASED = 'aggbased', +} + /** * A visualization type representing one specific type of "classical" * visualizations (i.e. not Lens visualizations). */ export interface VisType { + /** + * Visualization unique name + */ readonly name: string; + /** + * It is the displayed text on the wizard and the vis listing + */ readonly title: string; + /** + * If given, it will be diplayed on the wizard vis card as the main description. + */ readonly description?: string; + /** + * If given, it will be diplayed on the wizard vis card as a note in italic. + */ + readonly note: string; + /** + * If given, it will return the supported triggers for this vis. + */ readonly getSupportedTriggers?: () => Array; readonly isAccessible?: boolean; readonly requestHandler?: string | unknown; readonly responseHandler?: string | unknown; + /** + * It is the visualization icon, displayed on the wizard. + */ readonly icon?: IconType; + /** + * Except from an icon, an image can be passed + */ readonly image?: string; + /** + * Describes the visualization stage + */ readonly stage: 'experimental' | 'beta' | 'production'; + /** + * Describes the experience group that the visualization belongs. + * It can be on tools, aggregation based or promoted group. + */ + readonly group: VisGroups; + /** + * If given, it will be displayed on the wizard instead of the title. + * We use it because we want to differentiate the vis title from the + * way it is presented on the wizard + */ + readonly titleInWizard: string; readonly requiresSearch: boolean; readonly useCustomNoDataScreen: boolean; readonly hierarchicalData?: boolean | ((vis: { params: TVisParams }) => boolean); diff --git a/src/plugins/visualizations/public/vis_types/types_service.ts b/src/plugins/visualizations/public/vis_types/types_service.ts index 5d619064c240e9..27276eeb889238 100644 --- a/src/plugins/visualizations/public/vis_types/types_service.ts +++ b/src/plugins/visualizations/public/vis_types/types_service.ts @@ -20,7 +20,7 @@ import { visTypeAliasRegistry, VisTypeAlias } from './vis_type_alias_registry'; import { BaseVisType, BaseVisTypeOptions } from './base_vis_type'; import { ReactVisType, ReactVisTypeOptions } from './react_vis_type'; -import { VisType } from './types'; +import { VisType, VisGroups } from './types'; /** * Vis Types Service @@ -101,6 +101,21 @@ export class TypesService { * returns all registered aliases */ getAliases: visTypeAliasRegistry.get, + /** + * unregisters a visualization alias by its name + * alias is a visualization type without implementation, it just redirects somewhere in kibana + * @param {string} visTypeAliasName - visualization alias name + */ + unRegisterAlias: visTypeAliasRegistry.remove, + /** + * returns all visualizations of specific group + * @param {VisGroups} group - group type (aggbased | other | tools) + */ + getByGroup: (group: VisGroups) => { + return Object.values(this.types).filter((type) => { + return type.group === group; + }); + }, }; } diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 9733e9fd685499..fc5dfd4e123fb7 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -43,9 +43,9 @@ export interface VisualizationsAppExtension { }) => VisualizationListItem; } -export interface VisTypeAliasPromotion { +export interface VisTypeAliasPromoTooltip { description: string; - buttonText: string; + link: string; } export interface VisTypeAlias { @@ -54,8 +54,11 @@ export interface VisTypeAlias { name: string; title: string; icon: string; - promotion?: VisTypeAliasPromotion; + promotion?: boolean; + promoTooltip?: VisTypeAliasPromoTooltip; description: string; + note?: string; + disabled?: boolean; getSupportedTriggers?: () => Array; stage: 'experimental' | 'beta' | 'production'; @@ -65,11 +68,13 @@ export interface VisTypeAlias { }; } -const registry: VisTypeAlias[] = []; +let registry: VisTypeAlias[] = []; +const discardOnRegister: string[] = []; interface VisTypeAliasRegistry { get: () => VisTypeAlias[]; add: (newVisTypeAlias: VisTypeAlias) => void; + remove: (visTypeAliasName: string) => void; } export const visTypeAliasRegistry: VisTypeAliasRegistry = { @@ -78,6 +83,22 @@ export const visTypeAliasRegistry: VisTypeAliasRegistry = { if (registry.find((visTypeAlias) => visTypeAlias.name === newVisTypeAlias.name)) { throw new Error(`${newVisTypeAlias.name} already registered`); } - registry.push(newVisTypeAlias); + // if it exists on discardOnRegister array then we don't allow it to be registered + const isToBeDiscarded = discardOnRegister.some( + (aliasName) => aliasName === newVisTypeAlias.name + ); + if (!isToBeDiscarded) { + registry.push(newVisTypeAlias); + } + }, + remove: (visTypeAliasName) => { + const isAliasPresent = registry.find((visTypeAlias) => visTypeAlias.name === visTypeAliasName); + // in case the alias has not registered yet we store it on an array, in order to not allow it to + // be registered in case of a race condition + if (!isAliasPresent) { + discardOnRegister.push(visTypeAliasName); + } else { + registry = registry.filter((visTypeAlias) => visTypeAlias.name !== visTypeAliasName); + } }, }; diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap deleted file mode 100644 index 2089289b372a2a..00000000000000 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ /dev/null @@ -1,2141 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NewVisModal filter for visualization types should render as expected 1`] = ` - - - -
-
- -
-
-
- New Visualization -
-
-
-
-
-
-
-
-
- -
- - -
-
- -
-
-
-
-
- - 3 types found - -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-

- Select a visualization type -

-
-
-

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

-

- - promotion description - -

- -
-
-
-
-
-
-
-
- } - > - - -
-
- - - - - -
- - -
- -
- - New Visualization - -
-
-
-
-
- -
- -
- -
- -
- - -
-
- - - - -
- - - - - -
-
- - - - - -
-
-
-
-
-
-
-
- -
- - - - 3 types found - - - - -
    -
  • - - Vis alias with promotion - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with alias Url - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with search - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis Type 1 - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
-
-
-
-
-
-
-
- -
- -

- - Select a visualization type - -

-
- -
- - - -
-

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

-

- - promotion description - -

- - - - - -
-
-
-
- -
- -
- -
-
-
- - - - - -`; - -exports[`NewVisModal should render as expected 1`] = ` - - - -
-
- -
-
-
- New Visualization -
-
-
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
-
-

- Select a visualization type -

-
-
-

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

-

- - promotion description - -

- -
-
-
-
-
-
-
-
- } - > - - -
-
- - - - - -
- - -
- -
- - New Visualization - -
-
-
-
-
- -
- -
- -
- -
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
-
-
- -
- - - - -
    -
  • - - Vis alias with promotion - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis Type 1 - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with alias Url - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
  • - - Vis with search - - } - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} - > - - -
  • -
-
-
-
-
-
-
-
- -
- -

- - Select a visualization type - -

-
- -
- - - -
-

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

-

- - promotion description - -

- - - - - -
-
-
-
- -
- -
- -
-
-
- - - - - -`; diff --git a/src/plugins/visualizations/public/wizard/_index.scss b/src/plugins/visualizations/public/wizard/_index.scss deleted file mode 100644 index a10b4b1b347b7e..00000000000000 --- a/src/plugins/visualizations/public/wizard/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'dialog'; diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx new file mode 100644 index 00000000000000..3cbe6a0b604c65 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypesStart, VisType, VisGroups } from '../../vis_types'; +import { AggBasedSelection } from './agg_based_selection'; + +describe('AggBasedSelection', () => { + const defaultVisTypeParams = { + hidden: false, + visualization: class Controller { + public render = jest.fn(); + public destroy = jest.fn(); + }, + requiresSearch: false, + requestHandler: 'none', + responseHandler: 'none', + }; + const _visTypes = [ + { + name: 'vis1', + title: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }, + { + name: 'vis3', + title: 'Vis Type 3', + stage: 'production', + group: VisGroups.AGGBASED, + }, + { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }, + ] as VisType[]; + + const visTypes: TypesStart = { + get(id: string): VisType { + return _visTypes.find((vis) => vis.name === id) as VisType; + }, + all: () => { + return (_visTypes as unknown) as VisType[]; + }, + getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return _visTypes.filter((type) => { + return type.group === group; + }) as VisType[]; + }, + }; + + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call the toggleGroups if the user clicks the goBack link', () => { + const toggleGroups = jest.fn(); + const wrapper = mountWithIntl( + + ); + const aggBasedGroupCard = wrapper.find('[data-test-subj="goBackLink"]').at(0); + aggBasedGroupCard.simulate('click'); + expect(toggleGroups).toHaveBeenCalled(); + }); + + describe('filter for visualization types', () => { + it('should render as expected', () => { + const wrapper = mountWithIntl( + + ); + const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); + searchBox.simulate('change', { target: { value: 'with' } }); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true); + }); + }); +}); diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx new file mode 100644 index 00000000000000..5d11923ab68307 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.tsx @@ -0,0 +1,163 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { orderBy } from 'lodash'; +import React, { ChangeEvent } from 'react'; + +import { + EuiFieldSearch, + EuiFlexGrid, + EuiFlexItem, + EuiScreenReaderOnly, + EuiSpacer, + EuiIcon, + EuiCard, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; + +import { memoizeLast } from '../../legacy/memoize'; +import type { VisType, TypesStart } from '../../vis_types'; +import { VisGroups } from '../../vis_types'; +import { DialogNavigation } from '../dialog_navigation'; + +interface VisTypeListEntry { + type: VisType; + highlighted: boolean; +} + +interface AggBasedSelectionProps { + onVisTypeSelected: (visType: VisType) => void; + visTypesRegistry: TypesStart; + toggleGroups: (flag: boolean) => void; +} +interface AggBasedSelectionState { + query: string; +} + +class AggBasedSelection extends React.Component { + public state: AggBasedSelectionState = { + query: '', + }; + + private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); + + public render() { + const { query } = this.state; + const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); + return ( + <> + + + + + + + this.props.toggleGroups(true)} /> + + + + + {query && ( + type.highlighted).length, + }} + /> + )} + + + + {visTypes.map(this.renderVisType)} + + + + ); + } + + private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] { + const types = visTypes.getByGroup(VisGroups.AGGBASED).filter((type) => { + // Filter out hidden visualizations and visualizations that are only aggregations based + return !type.hidden; + }); + + let entries: VisTypeListEntry[]; + if (!query) { + entries = types.map((type) => ({ type, highlighted: false })); + } else { + const q = query.toLowerCase(); + entries = types.map((type) => { + const matchesQuery = + type.name.toLowerCase().includes(q) || + type.title.toLowerCase().includes(q) || + (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); + return { type, highlighted: matchesQuery }; + }); + } + + return orderBy(entries, ['highlighted', 'type.title'], ['desc', 'asc']); + } + + private renderVisType = (visType: VisTypeListEntry) => { + const isDisabled = this.state.query !== '' && !visType.highlighted; + const onClick = () => this.props.onVisTypeSelected(visType.type); + + return ( + + {visType.type.title}} + onClick={onClick} + data-test-subj={`visType-${visType.type.name}`} + data-vis-stage={visType.type.stage} + aria-label={`visType-${visType.type.name}`} + description={visType.type.description || ''} + layout="horizontal" + isDisabled={isDisabled} + icon={} + /> + + ); + }; + + private onQueryChange = (ev: ChangeEvent) => { + this.setState({ + query: ev.target.value, + }); + }; +} + +export { AggBasedSelection }; diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts b/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts new file mode 100644 index 00000000000000..c1dfab71cce499 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { AggBasedSelection } from './agg_based_selection'; diff --git a/src/plugins/visualizations/public/wizard/_dialog.scss b/src/plugins/visualizations/public/wizard/dialog.scss similarity index 81% rename from src/plugins/visualizations/public/wizard/_dialog.scss rename to src/plugins/visualizations/public/wizard/dialog.scss index 793951f9dd1ca3..f0cc8381a67aab 100644 --- a/src/plugins/visualizations/public/wizard/_dialog.scss +++ b/src/plugins/visualizations/public/wizard/dialog.scss @@ -1,84 +1,57 @@ -.visNewVisDialog { - max-width: 100vw; - background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E")); - background-repeat: no-repeat; - background-position: calc(100% + 1px) calc(100% + 1px); - background-size: 30%; -} +$modalWidth: $euiSizeL * 34; +$modalHeight: $euiSizeL * 30; -.visNewVisSearchDialog { - width: $euiSizeL * 30; - min-height: $euiSizeL * 25; -} - -.visNewVisDialog__body { - display: flex; - padding: $euiSizeM $euiSizeL 0; - min-height: 0; -} -.visNewVisDialog__list { - min-height: 0; -} +.visNewVisDialog { + @include euiBreakpoint('xs', 's') { + max-height: 100%; + } -.visNewVisDialog__searchWrapper { - flex-shrink: 0; + max-width: $modalWidth; + max-height: $modalHeight; + background: $euiColorEmptyShade; } -.visNewVisDialog__typesWrapper { - @include euiOverflowShadow; - max-width: $euiSizeXXL * 10; - min-height: 0; - margin-top: 2px; // Account for search field dropshadow - overflow: hidden; -} +.visNewVisDialog--aggbased { + @include euiBreakpoint('xs', 's') { + max-height: 100%; + } -.visNewVisDialog__types { - @include euiScrollBar; - // EUITODO: allow for more (calculated) widths of `EuiKeyPadMenu` - width: auto; - overflow-y: auto; - padding-top: $euiSize; - justify-content: center; - padding-bottom: $euiSize; + max-width: $modalWidth; + max-height: $modalHeight; + background-repeat: no-repeat; + background-position: calc(100% + 1px) calc(100% + 1px); + background-size: 30%; + // sass-lint:disable-block quotes space-after-comma + background-image: url(lightOrDarkTheme("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%23F5F7FA' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%23E6EBF2' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E","data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='313' height='461' viewBox='0 0 313 461'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath fill='%2318191E' d='M294.009,184.137 C456.386,184.137 588.018,315.77 588.018,478.146 C588.018,640.523 456.386,772.156 294.009,772.156 C131.632,772.156 0,640.523 0,478.146 C0,315.77 131.632,184.137 294.009,184.137 Z M294.009,384.552 C242.318,384.552 200.415,426.456 200.415,478.146 C200.415,529.837 242.318,571.741 294.009,571.741 C345.7,571.741 387.604,529.837 387.604,478.146 C387.604,426.456 345.7,384.552 294.009,384.552 Z'/%3E%3Cpath fill='%2315161B' d='M202.958,365.731 L202.958,380.991 L187.698,380.991 L187.698,365.731 L202.958,365.731 Z M202.958,327.073 L202.958,342.333 L187.698,342.333 L187.698,327.073 L202.958,327.073 Z M243.651,325.038 L243.651,340.298 L228.391,340.298 L228.391,325.038 L243.651,325.038 Z M243.651,286.379 L243.651,301.639 L228.391,301.639 L228.391,286.379 L243.651,286.379 Z M202.958,285.362 L202.958,300.622 L187.698,300.622 L187.698,285.362 L202.958,285.362 Z M284.345,284.345 L284.345,299.605 L269.085,299.605 L269.085,284.345 L284.345,284.345 Z M284.345,245.686 L284.345,260.946 L269.085,260.946 L269.085,245.686 L284.345,245.686 Z M243.651,244.669 L243.651,259.929 L228.391,259.929 L228.391,244.669 L243.651,244.669 Z M202.958,243.651 L202.958,258.911 L187.698,258.911 L187.698,243.651 L202.958,243.651 Z M284.345,203.975 L284.345,219.235 L269.085,219.235 L269.085,203.975 L284.345,203.975 Z M202.958,203.975 L202.958,219.235 L187.698,219.235 L187.698,203.975 L202.958,203.975 Z M243.651,202.958 L243.651,218.218 L228.391,218.218 L228.391,202.958 L243.651,202.958 Z M243.651,163.282 L243.651,178.542 L228.391,178.542 L228.391,163.282 L243.651,163.282 Z M202.958,163.282 L202.958,178.542 L187.698,178.542 L187.698,163.282 L202.958,163.282 Z M284.345,162.265 L284.345,177.525 L269.085,177.525 L269.085,162.265 L284.345,162.265 Z M284.345,122.589 L284.345,137.849 L269.085,137.849 L269.085,122.589 L284.345,122.589 Z M243.651,122.589 L243.651,137.849 L228.391,137.849 L228.391,122.589 L243.651,122.589 Z M202.958,122.589 L202.958,137.849 L187.698,137.849 L187.698,122.589 L202.958,122.589 Z M284.345,81.8954 L284.345,97.1554 L269.085,97.1554 L269.085,81.8954 L284.345,81.8954 Z M243.651,81.8954 L243.651,97.1554 L228.391,97.1554 L228.391,81.8954 L243.651,81.8954 Z M202.958,81.8954 L202.958,97.1554 L187.698,97.1554 L187.698,81.8954 L202.958,81.8954 Z M284.345,41.202 L284.345,56.462 L269.085,56.462 L269.085,41.202 L284.345,41.202 Z M243.651,41.202 L243.651,56.462 L228.391,56.462 L228.391,41.202 L243.651,41.202 Z M284.345,0.508789 L284.345,15.7688 L269.085,15.7688 L269.085,0.508789 L284.345,0.508789 Z'/%3E%3C/g%3E%3C/svg%3E")); } -.visNewVisDialog__description { - width: $euiSizeXL * 10; +.visNewVisSearchDialog { + min-width: $modalWidth; + min-height: $modalHeight; } -.visNewVisDialog__type:disabled { - opacity: 0.2; - pointer-events: none; +.visNewVisDialog__toolsCard { + background-color: transparent; } -.visNewVisDialog__typeImage { - @include size($euiSizeL); +.visNewVisDialog__groupsCard { + background-color: transparent; + box-shadow: none; } -@include euiBreakpoint('xs', 's') { - .visNewVisDialog { - background-image: none; - } - - .visNewVisDialog__typesWrapper { - max-width: none; - } - - .visNewVisDialog__types { - justify-content: flex-start; - } +.visNewVisDialog__groupsCardWrapper { + position: relative; - .visNewVisDialog__description { - display: none; + .visNewVisDialog__groupsCardLink { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin-top: - $euiSizeM; } -} -@include internetExplorerOnly { - .visNewVisDialog { - width: 820px; + // overrides EuiBetaBadge specificity + .visNewVisDialog__groupsCardBetaBadge { + background: $euiColorEmptyShade; + cursor: pointer; } - - .visNewVisDialog__body { - flex-basis: 800px; - } -} +} \ No newline at end of file diff --git a/src/plugins/visualizations/public/wizard/dialog_navigation.tsx b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx new file mode 100644 index 00000000000000..aa8e4c5580f523 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/dialog_navigation.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EuiLink, EuiIcon, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface DialogNavigationProps { + goBack: () => void; +} + +function DialogNavigation(props: DialogNavigationProps) { + return ( + <> + + + + + + + {i18n.translate('visualizations.newVisWizard.goBackLink', { + defaultMessage: 'Go back', + })} + + + + + + ); +} + +export { DialogNavigation }; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss new file mode 100644 index 00000000000000..ce46b872a97ee5 --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.scss @@ -0,0 +1,30 @@ +.visNewVisDialogGroupSelection__body { + // override EUI specificity + .euiModalBody__overflow { + padding: 0 !important; // sass-lint:disable-line no-important + } +} + +.visNewVisDialogGroupSelection__visGroups { + padding: $euiSizeS $euiSizeXL 0; +} + +.visNewVisDialogGroupSelection__footer { + @include euiBreakpoint('xs', 's') { + background: $euiColorEmptyShade; + } + + padding: 0 $euiSizeXL $euiSizeL; + background: $euiColorLightestShade; +} + +.visNewVisDialogGroupSelection__footerDescriptionList { + @include euiBreakpoint('xs', 's') { + padding-top: $euiSizeL; + } +} + +.visNewVisDialogGroupSelection__footerDescriptionListTitle { + // override EUI specificity + width: auto !important; // sass-lint:disable-line no-important +} diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx new file mode 100644 index 00000000000000..b357f42bbae8bc --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -0,0 +1,321 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypesStart, VisType, VisGroups } from '../../vis_types'; +import { GroupSelection } from './group_selection'; +import { DocLinksStart } from '../../../../../core/public'; + +describe('GroupSelection', () => { + const defaultVisTypeParams = { + hidden: false, + visualization: class Controller { + public render = jest.fn(); + public destroy = jest.fn(); + }, + requiresSearch: false, + requestHandler: 'none', + responseHandler: 'none', + }; + const _visTypes = [ + { + name: 'vis1', + title: 'Vis Type 1', + description: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + description: 'Vis Type 2', + group: VisGroups.PROMOTED, + stage: 'production', + ...defaultVisTypeParams, + }, + { + name: 'visWithAliasUrl', + title: 'Vis with alias Url', + aliasApp: 'aliasApp', + aliasPath: '#/aliasApp', + disabled: true, + promoTooltip: { + description: 'Learn More', + link: '#/anotherUrl', + }, + description: 'Vis with alias Url', + stage: 'production', + group: VisGroups.PROMOTED, + }, + { + name: 'visAliasWithPromotion', + title: 'Vis alias with promotion', + description: 'Vis alias with promotion', + stage: 'production', + group: VisGroups.PROMOTED, + aliasApp: 'anotherApp', + aliasPath: '#/anotherUrl', + promotion: true, + } as unknown, + ] as VisType[]; + + const visTypesRegistry = (visTypes: VisType[]): TypesStart => { + return { + get(id: string): VisType { + return (visTypes.find((vis) => vis.name === id) as unknown) as VisType; + }, + all: () => { + return (visTypes as unknown) as VisType[]; + }, + getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return (visTypes.filter((type) => { + return type.group === group; + }) as unknown) as VisType[]; + }, + }; + }; + + const docLinks = { + links: { + dashboard: { + guide: 'test', + }, + }, + }; + + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header title', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="groupModalHeader"]').at(0).text()).toBe( + 'New visualization' + ); + }); + + it('should not render the aggBased group card if no aggBased visualization is registered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(false); + }); + + it('should render the aggBased group card if an aggBased group vis is registered', () => { + const aggBasedVisType = { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true); + }); + + it('should not render the tools group card if no tools visualization is registered', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(false); + }); + + it('should render the tools group card if a tools group vis is registered', () => { + const toolsVisType = { + name: 'vis3', + title: 'Vis3', + stage: 'production', + group: VisGroups.TOOLS, + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true); + }); + + it('should call the toggleGroups if the aggBased group card is clicked', () => { + const toggleGroups = jest.fn(); + const aggBasedVisType = { + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, + stage: 'production', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + const aggBasedGroupCard = wrapper.find('[data-test-subj="visGroupAggBasedExploreLink"]').at(0); + aggBasedGroupCard.simulate('click'); + expect(toggleGroups).toHaveBeenCalled(); + }); + + it('should sort promoted visualizations first', () => { + const wrapper = mountWithIntl( + + ); + + const cards = [ + ...new Set( + wrapper.find('[data-test-subj^="visType-"]').map((card) => card.prop('data-test-subj')) + ), + ]; + + expect(cards).toEqual([ + 'visType-visAliasWithPromotion', + 'visType-vis1', + 'visType-vis2', + 'visType-visWithAliasUrl', + ]); + }); + + it('should render disabled aliases with a disabled class', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true); + expect( + wrapper + .find('[data-test-subj="visType-visWithAliasUrl"]') + .at(1) + .hasClass('euiCard-isDisabled') + ).toBe(true); + }); + + it('should render a basic badge with link for disabled aliases with promoTooltip', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe( + 'Learn More' + ); + }); + + it('should not show tools experimental visualizations if showExperimentalis false', () => { + const expVis = { + name: 'visExp', + title: 'Experimental Vis', + group: VisGroups.TOOLS, + stage: 'experimental', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); + }); + + it('should show tools experimental visualizations if showExperimental is true', () => { + const expVis = { + name: 'visExp', + title: 'Experimental Vis', + group: VisGroups.TOOLS, + stage: 'experimental', + ...defaultVisTypeParams, + }; + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); + }); +}); diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx new file mode 100644 index 00000000000000..8520b84cc42adb --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -0,0 +1,294 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useCallback, useMemo } from 'react'; +import { orderBy } from 'lodash'; +import { + EuiFlexGroup, + EuiFlexGrid, + EuiFlexItem, + EuiCard, + EuiIcon, + EuiModalHeader, + EuiModalBody, + EuiModalHeaderTitle, + EuiLink, + EuiText, + EuiSpacer, + EuiBetaBadge, + EuiTitle, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiDescriptionList, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DocLinksStart } from '../../../../../core/public'; +import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; +import type { VisType, TypesStart } from '../../vis_types'; +import { VisGroups } from '../../vis_types'; +import './group_selection.scss'; + +interface GroupSelectionProps { + onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; + visTypesRegistry: TypesStart; + docLinks: DocLinksStart; + toggleGroups: (flag: boolean) => void; + showExperimental: boolean; +} + +interface VisCardProps { + onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; + visType: VisType | VisTypeAlias; + showExperimental?: boolean | undefined; +} + +function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias { + return 'aliasPath' in type; +} + +function GroupSelection(props: GroupSelectionProps) { + const visualizeGuideLink = props.docLinks.links.dashboard.guide; + const promotedVisGroups = useMemo( + () => + orderBy( + [ + ...props.visTypesRegistry.getAliases(), + ...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED), + ], + ['promotion', 'title'], + ['asc', 'asc'] + ), + [props.visTypesRegistry] + ); + return ( + <> + + + + + + +
+ + + {promotedVisGroups.map((visType) => ( + + ))} + + +
+
+ + + {props.visTypesRegistry.getByGroup(VisGroups.AGGBASED).length > 0 && ( + + props.toggleGroups(false)} + title={ + + {i18n.translate('visualizations.newVisWizard.aggBasedGroupTitle', { + defaultMessage: 'Aggregation based', + })} + + } + data-test-subj="visGroup-aggbased" + description={i18n.translate( + 'visualizations.newVisWizard.aggBasedGroupDescription', + { + defaultMessage: + 'Use our classic visualize library to create charts based on aggregations.', + } + )} + icon={} + className="visNewVisDialog__groupsCard" + > + props.toggleGroups(false)} + > + + {i18n.translate('visualizations.newVisWizard.exploreOptionLinkText', { + defaultMessage: 'Explore options', + })}{' '} + + + + + + )} + {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).length > 0 && ( + + + + + {i18n.translate('visualizations.newVisWizard.toolsGroupTitle', { + defaultMessage: 'Tools', + })} + + + +
+ {props.visTypesRegistry.getByGroup(VisGroups.TOOLS).map((visType) => ( + + ))} +
+
+ )} +
+ + + + + + + + + + + +
+
+ + ); +} + +const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => { + const onClick = useCallback(() => { + onVisTypeSelected(visType); + }, [onVisTypeSelected, visType]); + const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled; + const betaBadgeContent = + shouldDisableCard && 'promoTooltip' in visType ? ( + + + + ) : undefined; + return ( + + {betaBadgeContent} + + {'titleInWizard' in visType && visType.titleInWizard + ? visType.titleInWizard + : visType.title} + + } + onClick={onClick} + isDisabled={shouldDisableCard} + data-test-subj={`visType-${visType.name}`} + data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'} + aria-label={`visType-${visType.name}`} + description={ + <> + {visType.description || ''} + {visType.note || ''} + + } + layout="horizontal" + icon={} + className="visNewVisDialog__groupsCard" + /> + + ); +}; + +const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardProps) => { + const onClick = useCallback(() => { + onVisTypeSelected(visType); + }, [onVisTypeSelected, visType]); + // hide the experimental visualization if lab mode is not enabled + if (!showExperimental && visType.stage === 'experimental') { + return null; + } + return ( + + + + + + + + + {'titleInWizard' in visType && visType.titleInWizard + ? visType.titleInWizard + : visType.title} + + + {visType.stage === 'experimental' && ( + + + + )} + + + {visType.description} + + + + ); +}; + +export { GroupSelection }; diff --git a/src/plugins/visualizations/public/wizard/group_selection/index.ts b/src/plugins/visualizations/public/wizard/group_selection/index.ts new file mode 100644 index 00000000000000..80bd3dda7ac40f --- /dev/null +++ b/src/plugins/visualizations/public/wizard/group_selection/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { GroupSelection } from './group_selection'; diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 51bcfed2016874..eea364b754e31e 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -19,9 +19,9 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { TypesStart, VisType } from '../vis_types'; -import { NewVisModal } from './new_vis_modal'; -import { ApplicationStart, SavedObjectsStart } from '../../../../core/public'; +import { TypesStart, VisType, VisGroups } from '../vis_types'; +import NewVisModal from './new_vis_modal'; +import { ApplicationStart, SavedObjectsStart, DocLinksStart } from '../../../../core/public'; import { embeddablePluginMock } from '../../../embeddable/public/mocks'; describe('NewVisModal', () => { @@ -36,31 +36,41 @@ describe('NewVisModal', () => { responseHandler: 'none', }; const _visTypes = [ - { name: 'vis', title: 'Vis Type 1', stage: 'production', ...defaultVisTypeParams }, - { name: 'visExp', title: 'Experimental Vis', stage: 'experimental', ...defaultVisTypeParams }, { - name: 'visWithSearch', - title: 'Vis with search', + name: 'vis', + title: 'Vis Type 1', + stage: 'production', + group: VisGroups.PROMOTED, + ...defaultVisTypeParams, + }, + { + name: 'vis2', + title: 'Vis Type 2', + group: VisGroups.PROMOTED, stage: 'production', ...defaultVisTypeParams, }, + { + name: 'vis3', + title: 'Vis3', + stage: 'production', + group: VisGroups.TOOLS, + ...defaultVisTypeParams, + }, { name: 'visWithAliasUrl', title: 'Vis with alias Url', stage: 'production', + group: VisGroups.PROMOTED, aliasApp: 'otherApp', aliasPath: '#/aliasUrl', }, { - name: 'visAliasWithPromotion', - title: 'Vis alias with promotion', + name: 'visWithSearch', + title: 'Vis with search', + group: VisGroups.AGGBASED, stage: 'production', - aliasApp: 'anotherApp', - aliasPath: '#/anotherUrl', - promotion: { - description: 'promotion description', - buttonText: 'another app', - }, + ...defaultVisTypeParams, }, ]; const visTypes: TypesStart = { @@ -71,10 +81,23 @@ describe('NewVisModal', () => { return (_visTypes as unknown) as VisType[]; }, getAliases: () => [], + unRegisterAlias: () => [], + getByGroup: (group: VisGroups) => { + return (_visTypes.filter((type) => { + return type.group === group; + }) as unknown) as VisType[]; + }, }; const addBasePath = (url: string) => `testbasepath${url}`; const settingsGet = jest.fn(); const uiSettings: any = { get: settingsGet }; + const docLinks = { + links: { + dashboard: { + guide: 'test', + }, + }, + }; beforeAll(() => { Object.defineProperty(window, 'location', { @@ -88,7 +111,7 @@ describe('NewVisModal', () => { jest.clearAllMocks(); }); - it('should render as expected', () => { + it('should show the aggbased group but not the visualization assigned to this group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(false); }); - it('should show a button for regular visualizations', () => { + it('should show the tools group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true); }); - it('should sort promoted visualizations first', () => { + it('should display the visualizations of the other group', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - expect( - wrapper - .find('button[data-test-subj^="visType-"]') - .map((button) => button.prop('data-test-subj')) - ).toEqual([ - 'visType-visAliasWithPromotion', - 'visType-vis', - 'visType-visWithAliasUrl', - 'visType-visWithSearch', - ]); + expect(wrapper.find('[data-test-subj="visType-vis2"]').exists()).toBe(true); }); describe('open editor', () => { @@ -152,11 +170,12 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0); + visCard.simulate('click'); expect(window.location.assign).toBeCalledWith('testbasepath/app/visualize#/create?type=vis'); }); @@ -170,11 +189,12 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-vis"]').at(0); + visCard.simulate('click'); expect(window.location.assign).toBeCalledWith( 'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42' ); @@ -194,12 +214,13 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={({ navigateToApp } as unknown) as ApplicationStart} + docLinks={docLinks as DocLinksStart} stateTransfer={stateTransfer} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0); + visCard.simulate('click'); expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', { path: '#/aliasUrl', state: { originatingApp: 'coolJestTestApp' }, @@ -219,17 +240,18 @@ describe('NewVisModal', () => { addBasePath={addBasePath} uiSettings={uiSettings} application={({ navigateToApp } as unknown) as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); - visButton.simulate('click'); + const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').at(0); + visCard.simulate('click'); expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' }); expect(onClose).toHaveBeenCalled(); }); }); - describe('filter for visualization types', () => { + describe('aggBased visualizations', () => { it('should render as expected', () => { const wrapper = mountWithIntl( { addBasePath={addBasePath} uiSettings={uiSettings} application={{} as ApplicationStart} + docLinks={docLinks as DocLinksStart} savedObjects={{} as SavedObjectsStart} /> ); - const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); - searchBox.simulate('change', { target: { value: 'with' } }); - expect(wrapper).toMatchSnapshot(); - }); - }); - - describe('experimental visualizations', () => { - it('should not show experimental visualizations if visualize:enableLabs is false', () => { - settingsGet.mockReturnValue(false); - const wrapper = mountWithIntl( - null} - visTypesRegistry={visTypes} - addBasePath={addBasePath} - uiSettings={uiSettings} - application={{} as ApplicationStart} - savedObjects={{} as SavedObjectsStart} - /> - ); - expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); - }); - - it('should show experimental visualizations if visualize:enableLabs is true', () => { - settingsGet.mockReturnValue(true); - const wrapper = mountWithIntl( - null} - visTypesRegistry={visTypes} - addBasePath={addBasePath} - uiSettings={uiSettings} - application={{} as ApplicationStart} - savedObjects={{} as SavedObjectsStart} - /> - ); - expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); + const aggBasedGroupCard = wrapper + .find('[data-test-subj="visGroupAggBasedExploreLink"]') + .at(0); + aggBasedGroupCard.simulate('click'); + expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true); }); }); }); diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 4bedd3eb1c22ae..fbd4e6ef80d5a2 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -23,13 +23,20 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics'; -import { ApplicationStart, IUiSettingsClient, SavedObjectsStart } from '../../../../core/public'; +import { + ApplicationStart, + IUiSettingsClient, + SavedObjectsStart, + DocLinksStart, +} from '../../../../core/public'; import { SearchSelection } from './search_selection'; -import { TypeSelection } from './type_selection'; -import { TypesStart, VisType, VisTypeAlias } from '../vis_types'; +import { GroupSelection } from './group_selection'; +import { AggBasedSelection } from './agg_based_selection'; +import type { TypesStart, VisType, VisTypeAlias } from '../vis_types'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; import { EmbeddableStateTransfer } from '../../../embeddable/public'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; +import './dialog.scss'; interface TypeSelectionProps { isOpen: boolean; @@ -38,6 +45,7 @@ interface TypeSelectionProps { editorParams?: string[]; addBasePath: (path: string) => string; uiSettings: IUiSettingsClient; + docLinks: DocLinksStart; savedObjects: SavedObjectsStart; usageCollection?: UsageCollectionSetup; application: ApplicationStart; @@ -48,6 +56,7 @@ interface TypeSelectionProps { interface TypeSelectionState { showSearchVisModal: boolean; + showGroups: boolean; visType?: VisType; } @@ -72,6 +81,7 @@ class NewVisModal extends React.Component @@ -101,19 +113,21 @@ class NewVisModal extends React.Component this.setState({ showSearchVisModal: false })} /> ) : ( - this.setState({ showGroups: flag })} /> ); @@ -185,4 +199,6 @@ class NewVisModal extends React.Component void; visType: VisType; uiSettings: IUiSettingsClient; savedObjects: SavedObjectsStart; + goBack: () => void; } export class SearchSelection extends React.Component { @@ -54,6 +56,7 @@ export class SearchSelection extends React.Component { + import('./new_vis_modal')); + export interface ShowNewVisModalParams { editorParams?: string[]; onClose?: () => void; @@ -66,20 +68,29 @@ export function showNewVisModal({ document.body.appendChild(container); const element = ( - + + + + } + > + + ); ReactDOM.render(element, container); diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx deleted file mode 100644 index a5b6e8039ba6d5..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.test.tsx +++ /dev/null @@ -1,73 +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 React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisHelp } from './new_vis_help'; - -describe('NewVisHelp', () => { - it('should render as expected', () => { - expect( - shallowWithIntl( - {}} - /> - ) - ).toMatchInlineSnapshot(` - -

- -

-

- - Look at this fancy new thing!!! - -

- - Do it now! - -
- `); - }); -}); diff --git a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx b/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx deleted file mode 100644 index 5b226a889408f0..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/new_vis_help.tsx +++ /dev/null @@ -1,57 +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 { FormattedMessage } from '@kbn/i18n/react'; -import React, { Fragment } from 'react'; -import { EuiText, EuiButton } from '@elastic/eui'; -import { VisTypeAlias } from '../../vis_types'; - -interface Props { - promotedTypes: VisTypeAlias[]; - onPromotionClicked: (visType: VisTypeAlias) => void; -} - -export function NewVisHelp(props: Props) { - return ( - -

- -

- {props.promotedTypes.map((t) => ( - -

- {t.promotion!.description} -

- props.onPromotionClicked(t)} - fill - size="s" - iconType="popout" - iconSide="right" - > - {t.promotion!.buttonText} - -
- ))} -
- ); -} diff --git a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx deleted file mode 100644 index 8c086ed132ae48..00000000000000 --- a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx +++ /dev/null @@ -1,289 +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 { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { orderBy } from 'lodash'; -import React, { ChangeEvent } from 'react'; - -import { - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiKeyPadMenu, - EuiKeyPadMenuItem, - EuiModalHeader, - EuiModalHeaderTitle, - EuiScreenReaderOnly, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; - -import { memoizeLast } from '../../legacy/memoize'; -import { VisTypeAlias } from '../../vis_types/vis_type_alias_registry'; -import { NewVisHelp } from './new_vis_help'; -import { VisHelpText } from './vis_help_text'; -import { VisTypeIcon } from './vis_type_icon'; -import { VisType, TypesStart } from '../../vis_types'; - -interface VisTypeListEntry { - type: VisType | VisTypeAlias; - highlighted: boolean; -} - -interface TypeSelectionProps { - addBasePath: (path: string) => string; - onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; - visTypesRegistry: TypesStart; - showExperimental: boolean; -} - -interface HighlightedType { - name: string; - title: string; - description?: string; - highlightMsg?: string; -} - -interface TypeSelectionState { - highlightedType: HighlightedType | null; - query: string; -} - -function isVisTypeAlias(type: VisType | VisTypeAlias): type is VisTypeAlias { - return 'aliasPath' in type; -} - -class TypeSelection extends React.Component { - public state: TypeSelectionState = { - highlightedType: null, - query: '', - }; - - private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); - - public render() { - const { query, highlightedType } = this.state; - const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); - return ( - - - - - - -
- - - - - - - - - - {query && ( - type.highlighted).length, - }} - /> - )} - - - - {visTypes.map(this.renderVisType)} - - - - - - {highlightedType ? ( - - ) : ( - - -

- -

-
- - t.type) - .filter((t): t is VisTypeAlias => isVisTypeAlias(t) && Boolean(t.promotion))} - onPromotionClicked={this.props.onVisTypeSelected} - /> -
- )} -
-
-
-
- ); - } - - private filteredVisTypes(visTypes: TypesStart, query: string): VisTypeListEntry[] { - const types = visTypes.all().filter((type) => { - // Filter out all lab visualizations if lab mode is not enabled - if (!this.props.showExperimental && type.stage === 'experimental') { - return false; - } - - // Filter out hidden visualizations - if (type.hidden) { - return false; - } - - return true; - }); - - const allTypes = [...types, ...visTypes.getAliases()]; - - let entries: VisTypeListEntry[]; - if (!query) { - entries = allTypes.map((type) => ({ type, highlighted: false })); - } else { - const q = query.toLowerCase(); - entries = allTypes.map((type) => { - const matchesQuery = - type.name.toLowerCase().includes(q) || - type.title.toLowerCase().includes(q) || - (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); - return { type, highlighted: matchesQuery }; - }); - } - - return orderBy( - entries, - ['highlighted', 'type.promotion', 'type.title'], - ['desc', 'asc', 'asc'] - ); - } - - private renderVisType = (visType: VisTypeListEntry) => { - let stage = {}; - let highlightMsg; - if (!isVisTypeAlias(visType.type) && visType.type.stage === 'experimental') { - stage = { - betaBadgeLabel: i18n.translate('visualizations.newVisWizard.experimentalTitle', { - defaultMessage: 'Experimental', - }), - betaBadgeTooltipContent: i18n.translate('visualizations.newVisWizard.experimentalTooltip', { - defaultMessage: - 'This visualization might be changed or removed in a future release and is not subject to the support SLA.', - }), - }; - highlightMsg = i18n.translate('visualizations.newVisWizard.experimentalDescription', { - defaultMessage: - 'This visualization is experimental. The design and implementation are less mature than stable visualizations and might be subject to change.', - }); - } else if (isVisTypeAlias(visType.type) && visType.type.stage === 'beta') { - const aliasDescription = i18n.translate('visualizations.newVisWizard.betaDescription', { - defaultMessage: - 'This visualization is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features', - }); - stage = { - betaBadgeLabel: i18n.translate('visualizations.newVisWizard.betaTitle', { - defaultMessage: 'Beta', - }), - betaBadgeTooltipContent: aliasDescription, - }; - highlightMsg = aliasDescription; - } - - const isDisabled = this.state.query !== '' && !visType.highlighted; - const onClick = () => this.props.onVisTypeSelected(visType.type); - - const highlightedType: HighlightedType = { - title: visType.type.title, - name: visType.type.name, - description: visType.type.description, - highlightMsg, - }; - - return ( - {visType.type.title}} - onClick={onClick} - onFocus={() => this.setHighlightType(highlightedType)} - onMouseEnter={() => this.setHighlightType(highlightedType)} - onMouseLeave={() => this.setHighlightType(null)} - onBlur={() => this.setHighlightType(null)} - className="visNewVisDialog__type" - data-test-subj={`visType-${visType.type.name}`} - data-vis-stage={!isVisTypeAlias(visType.type) ? visType.type.stage : 'alias'} - disabled={isDisabled} - aria-describedby={`visTypeDescription-${visType.type.name}`} - {...stage} - > - - - ); - }; - - private setHighlightType(highlightedType: HighlightedType | null) { - this.setState({ - highlightedType, - }); - } - - private onQueryChange = (ev: ChangeEvent) => { - this.setState({ - query: ev.target.value, - }); - }; -} - -export { TypeSelection }; diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index c2e36b4a669ffd..e5da46644672bb 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -334,6 +334,70 @@ export default function ({ getService }) { }); }); + describe('searching for special characters', () => { + before(() => esArchiver.load('saved_objects/find_edgecases')); + after(() => esArchiver.unload('saved_objects/find_edgecases')); + + it('can search for objects with dashes', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-vis*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search with the prefix search character just after a special one', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'my-*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['my-visualization']); + })); + + it('can search for objects with asterisk', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'some*vi*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql(['some*visualization']); + })); + + it('can still search tokens by prefix', async () => + await supertest + .get('/api/saved_objects/_find') + .query({ + type: 'visualization', + search_fields: 'title', + search: 'visuali*', + }) + .expect(200) + .then((resp) => { + const savedObjects = resp.body.saved_objects; + expect(savedObjects.map((so) => so.attributes.title)).to.eql([ + 'my-visualization', + 'some*visualization', + ]); + })); + }); + describe('without kibana index', () => { before( async () => diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json new file mode 100644 index 00000000000000..0c8b35fd3f4994 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/data.json @@ -0,0 +1,93 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-dash", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "my-visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:title-with-asterisk", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "some*visualization", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-1", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "visualization:noise-2", + "source": { + "type": "visualization", + "updated_at": "2017-09-21T18:51:23.794Z", + "visualization": { + "title": "Just some noise in the dataset", + "visState": "{}", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "references": [] + } + } +} + diff --git a/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json new file mode 100644 index 00000000000000..e601c43431437c --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/saved_objects/find_edgecases/mappings.json @@ -0,0 +1,267 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "namespace": { + "type": "keyword" + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index f5c2496a9a5aaf..c315828c594fa0 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -42,10 +42,11 @@ export default function ({ getService, getPageObjects }) { }); describe('add new visualization link', () => { - it('adds new visualiztion via the top nav link', async () => { + it('adds new visualization via the top nav link', async () => { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await PageObjects.dashboard.switchToEditMode(); await dashboardAddPanel.clickCreateNewLink(); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( @@ -63,6 +64,7 @@ export default function ({ getService, getPageObjects }) { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await dashboardAddPanel.ensureAddPanelIsShowing(); await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess( diff --git a/test/functional/apps/dashboard/view_edit.js b/test/functional/apps/dashboard/view_edit.js index 589a46b7e9d08d..a6d81da131751a 100644 --- a/test/functional/apps/dashboard/view_edit.js +++ b/test/functional/apps/dashboard/view_edit.js @@ -134,6 +134,7 @@ export default function ({ getService, getPageObjects }) { await dashboardAddPanel.ensureAddPanelIsShowing(); await dashboardAddPanel.clickAddNewEmbeddableLink('visualization'); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visualize.saveVisualizationExpectSuccess('new viz panel', { diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index e597cc14654bcb..3c9996ca44ff8c 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -32,7 +32,8 @@ export default function ({ getService, getPageObjects }) { defaultIndex: 'logstash-*', }; - describe('discover test', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/82915 + describe.skip('discover test', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.load('discover'); diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js index 38ed3edc7deb55..a648dae99158d6 100644 --- a/test/functional/apps/getting_started/_shakespeare.js +++ b/test/functional/apps/getting_started/_shakespeare.js @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }) { */ it('should create initial vertical bar chart', async function () { log.debug('create shakespeare vertical bar chart'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch('shakespeare'); await PageObjects.visChart.waitForVisualization(); diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index 9ac2160a359da1..b943e070e1768a 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) { const initAreaChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); @@ -390,7 +390,7 @@ export default function ({ getService, getPageObjects }) { const toTime = 'Jan 1, 2020 @ 00:00:00.000'; it('should render a yearly area with 12 svg paths', async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); @@ -413,7 +413,7 @@ export default function ({ getService, getPageObjects }) { }); it('should render monthly areas with 168 svg paths', async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickAreaChart'); await PageObjects.visualize.clickAreaChart(); log.debug('clickNewSearch'); diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts index ecb7e9630c2c65..4864fcbf3af095 100644 --- a/test/functional/apps/visualize/_chart_types.ts +++ b/test/functional/apps/visualize/_chart_types.ts @@ -33,10 +33,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.navigateToNewVisualization(); }); - it('should show the correct chart types', async function () { + it('should show the promoted vis types for the first step', async function () { + const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB']; + log.debug('oss= ' + isOss); + + // find all the chart types and make sure there all there + const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); + log.debug('returned chart types = ' + chartTypes); + log.debug('expected chart types = ' + expectedChartTypes); + expect(chartTypes).to.eql(expectedChartTypes); + }); + + it('should show the correct agg based chart types', async function () { + await PageObjects.visualize.clickAggBasedVisualizations(); let expectedChartTypes = [ 'Area', - 'Controls', 'Coordinate Map', 'Data Table', 'Gauge', @@ -44,18 +55,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Heat Map', 'Horizontal Bar', 'Line', - 'Markdown', 'Metric', 'Pie', 'Region Map', - 'TSVB', 'Tag Cloud', 'Timelion', - 'Vega', 'Vertical Bar', ]; if (!isOss) { - expectedChartTypes.push('Maps', 'Lens'); expectedChartTypes = _.remove(expectedChartTypes, function (n) { return n !== 'Coordinate Map'; }); diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index a1389a69666ae1..bd7511d373b90e 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); @@ -107,7 +107,7 @@ export default function ({ getService, getPageObjects }) { } // load a plain table - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -152,7 +152,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -167,7 +167,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -189,7 +189,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with date histogram', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -224,7 +224,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with top hits', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -238,7 +238,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data for a data table with range agg', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -257,7 +257,7 @@ export default function ({ getService, getPageObjects }) { describe('otherBucket', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -295,7 +295,7 @@ export default function ({ getService, getPageObjects }) { describe('metricsOnAllLevels', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -389,7 +389,7 @@ export default function ({ getService, getPageObjects }) { describe('split tables', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js index fd06257a91ff4f..f45e40970a57fd 100644 --- a/test/functional/apps/visualize/_data_table_nontimeindex.js +++ b/test/functional/apps/visualize/_data_table_nontimeindex.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show correct data when using average pipeline aggregation', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }) { describe('data table with date histogram', async () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts index 9fb6d793eb7d9d..cdcf08d2514e5d 100644 --- a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts +++ b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts @@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); log.debug('clickNewSearch'); diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js index 5316bf51fd48c2..773bd63d8892f4 100644 --- a/test/functional/apps/visualize/_embedding_chart.js +++ b/test/functional/apps/visualize/_embedding_chart.js @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }) { describe('embedding', () => { describe('a data table', () => { before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_experimental_vis.js b/test/functional/apps/visualize/_experimental_vis.js index e87a36434b2314..c85d66eed12d21 100644 --- a/test/functional/apps/visualize/_experimental_vis.js +++ b/test/functional/apps/visualize/_experimental_vis.js @@ -27,7 +27,7 @@ export default ({ getService, getPageObjects }) => { describe('experimental visualizations', () => { beforeEach(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.waitForVisualizationSelectPage(); }); diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 0f870b1fb545fb..06f5913aec8144 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { describe('gauge chart', function indexPatternCreation() { async function initGaugeVis() { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickGauge'); await PageObjects.visualize.clickGauge(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js index 98a0104629c851..e48173c290372a 100644 --- a/test/functional/apps/visualize/_heatmap_chart.js +++ b/test/functional/apps/visualize/_heatmap_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickHeatmapChart'); await PageObjects.visualize.clickHeatmapChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_histogram_request_start.js b/test/functional/apps/visualize/_histogram_request_start.js index f6440cfbd76ffc..8489cffa805da2 100644 --- a/test/functional/apps/visualize/_histogram_request_start.js +++ b/test/functional/apps/visualize/_histogram_request_start.js @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }) { describe('histogram agg onSearchRequestStart', function () { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickDataTable'); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js index 104184050e838b..426e61d9636ad8 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }) { describe('inspector', function describeIndexTests() { before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 8dfc4d352b1331..8c6dbd3461a4f5 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) { const initLineChart = async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); @@ -244,7 +244,7 @@ export default function ({ getService, getPageObjects }) { describe('pipeline aggregations', () => { before(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_linked_saved_searches.ts b/test/functional/apps/visualize/_linked_saved_searches.ts index 4151e0e9b471c5..a5a9c47d3d0102 100644 --- a/test/functional/apps/visualize/_linked_saved_searches.ts +++ b/test/functional/apps/visualize/_linked_saved_searches.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should create a visualization from a saved search', async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickSavedSearch(savedSearchName); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index e02dac11d9e982..483a0688d6acad 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { describe('metric chart', function () { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickMetric'); await PageObjects.visualize.clickMetric(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js index b68a88c4f97f33..eef0f90005a436 100644 --- a/test/functional/apps/visualize/_pie_chart.js +++ b/test/functional/apps/visualize/_pie_chart.js @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }) { const vizName1 = 'Visualization PieChart'; before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }) { it('should show other and missing bucket', async function () { const expectedTableData = ['win 8', 'win xp', 'win 7', 'ios', 'Missing', 'Other']; - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -292,7 +292,7 @@ export default function ({ getService, getPageObjects }) { describe('empty time window', () => { it('should show no data message when no data on selected timerange', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -321,7 +321,7 @@ export default function ({ getService, getPageObjects }) { describe('multi series slice', () => { before(async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -443,7 +443,7 @@ export default function ({ getService, getPageObjects }) { }); it('should still showing pie chart when a subseries have zero data', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickPieChart'); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); @@ -471,7 +471,7 @@ export default function ({ getService, getPageObjects }) { describe('split chart', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickPieChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index c88670ee8b7414..a671cb82ab70fe 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }) { async function initChart() { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickLineChart'); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); @@ -197,7 +197,7 @@ export default function ({ getService, getPageObjects }) { describe('show values on chart', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -232,7 +232,7 @@ export default function ({ getService, getPageObjects }) { const customLabel = 'myLabel'; const axisTitle = 'myTitle'; before(async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visEditor.selectYAxisAggregation('Average', 'bytes', customLabel, 1); diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js index f2fe1527c97c28..eee73d2fe76f9b 100644 --- a/test/functional/apps/visualize/_region_map.js +++ b/test/functional/apps/visualize/_region_map.js @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickRegionMap'); await PageObjects.visualize.clickRegionMap(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js index b5c575edb8a0a1..7b1671aaeb2f26 100644 --- a/test/functional/apps/visualize/_tag_cloud.js +++ b/test/functional/apps/visualize/_tag_cloud.js @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }) { before(async function () { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTagCloud'); await PageObjects.visualize.clickTagCloud(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js index 56585ccd725d80..3ae72fe73a5fda 100644 --- a/test/functional/apps/visualize/_tile_map.js +++ b/test/functional/apps/visualize/_tile_map.js @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); @@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); @@ -240,7 +240,7 @@ export default function ({ getService, getPageObjects }) { await browser.setWindowSize(1280, 1000); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickTileMap'); await PageObjects.visualize.clickTileMap(); await PageObjects.visualize.clickNewSearch(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index ff0423eb531da9..5ecbf3f720ee67 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }) { const initBarChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }) { }); it('should not filter out first label after rotation of the chart', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }) { describe('bar charts range on x axis', () => { it('should individual bars for each configured range', async function () { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js index f95781c9bbb337..7fb123f2e304ab 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js +++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { const initBarChart = async () => { log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch( diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 9619c81370cd8d..b58acd0bd90592 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -47,6 +47,14 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide await listingTable.clickNewButton('createVisualizationPromptButton'); } + public async clickAggBasedVisualizations() { + await testSubjects.click('visGroupAggBasedExploreLink'); + } + + public async goBackToGroups() { + await testSubjects.click('goBackLink'); + } + public async createVisualizationPromptButton() { await testSubjects.click('createVisualizationPromptButton'); } @@ -59,6 +67,21 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide .map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim()); } + public async getPromotedVisTypes() { + const chartTypeField = await testSubjects.find('visNewDialogGroups'); + const $ = await chartTypeField.parseDomContent(); + const promotedVisTypes: string[] = []; + $('button') + .toArray() + .forEach((chart) => { + const title = $(chart).findTestSubject('visTypeTitle').text().trim(); + if (title) { + promotedVisTypes.push(title); + } + }); + return promotedVisTypes; + } + public async waitForVisualizationSelectPage() { await retry.try(async () => { const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes'); @@ -68,10 +91,27 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide }); } + public async waitForGroupsSelectPage() { + await retry.try(async () => { + const visualizeSelectGroupStep = await testSubjects.find('visNewDialogGroups'); + if (!(await visualizeSelectGroupStep.isDisplayed())) { + throw new Error('wait for vis groups select step'); + } + }); + } + public async navigateToNewVisualization() { await common.navigateToApp('visualize'); await header.waitUntilLoadingHasFinished(); await this.clickNewVisualization(); + await this.waitForGroupsSelectPage(); + } + + public async navigateToNewAggBasedVisualization() { + await common.navigateToApp('visualize'); + await header.waitUntilLoadingHasFinished(); + await this.clickNewVisualization(); + await this.clickAggBasedVisualizations(); await this.waitForVisualizationSelectPage(); } diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index a5c16010d3ebaa..b35ef1e8f2f9a9 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -147,6 +147,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F await PageObjects.dashboard.switchToEditMode(); } await this.ensureNewVisualizationDialogIsShowing(); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickMetric(); await find.clickByCssSelector('li.euiListGroupItem:nth-of-type(2)'); await testSubjects.exists('visualizeSaveButton'); diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js index 0846780f75ff61..1971754d5304e5 100644 --- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js +++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { describe('self changing vis', function describeIndexTests() { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickVisType('self_changing_vis'); }); diff --git a/x-pack/plugins/alerts/common/alert.ts b/x-pack/plugins/alerts/common/alert.ts index 79e6bb8f2cbbaf..97a9a58400e385 100644 --- a/x-pack/plugins/alerts/common/alert.ts +++ b/x-pack/plugins/alerts/common/alert.ts @@ -20,13 +20,12 @@ export interface IntervalSchedule extends SavedObjectAttributes { export const AlertExecutionStatusValues = ['ok', 'active', 'error', 'pending', 'unknown'] as const; export type AlertExecutionStatuses = typeof AlertExecutionStatusValues[number]; -export const AlertExecutionStatusErrorReasonValues = [ - 'read', - 'decrypt', - 'execute', - 'unknown', -] as const; -export type AlertExecutionStatusErrorReasons = typeof AlertExecutionStatusErrorReasonValues[number]; +export enum AlertExecutionStatusErrorReasons { + Read = 'read', + Decrypt = 'decrypt', + Execute = 'execute', + Unknown = 'unknown', +} export interface AlertExecutionStatus { status: AlertExecutionStatuses; @@ -74,3 +73,24 @@ export interface Alert { } export type SanitizedAlert = Omit; + +export enum HealthStatus { + OK = 'ok', + Warning = 'warn', + Error = 'error', +} + +export interface AlertsHealth { + decryptionHealth: { + status: HealthStatus; + timestamp: string; + }; + executionHealth: { + status: HealthStatus; + timestamp: string; + }; + readHealth: { + status: HealthStatus; + timestamp: string; + }; +} diff --git a/x-pack/plugins/alerts/common/alert_instance_summary.ts b/x-pack/plugins/alerts/common/alert_instance_summary.ts index 08c3b2fc2c241c..1aa183a141eab6 100644 --- a/x-pack/plugins/alerts/common/alert_instance_summary.ts +++ b/x-pack/plugins/alerts/common/alert_instance_summary.ts @@ -27,5 +27,6 @@ export interface AlertInstanceSummary { export interface AlertInstanceStatus { status: AlertInstanceStatusValues; muted: boolean; + actionGroupId?: string; activeStartDate?: string; } diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts index ab71f77a049f66..65aeec840da7e2 100644 --- a/x-pack/plugins/alerts/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertsHealth } from './alert'; + export * from './alert'; export * from './alert_type'; export * from './alert_instance'; @@ -19,6 +21,7 @@ export interface ActionGroup { export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; hasPermanentEncryptionKey: boolean; + alertingFrameworkHeath: AlertsHealth; } export const BASE_ALERT_API_PATH = '/api/alerts'; diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index a53e49337f3857..9cb2a33222d23d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -118,12 +118,12 @@ describe('getAlertInstanceSummary()', () => { .addExecute() .addNewInstance('instance-currently-active') .addNewInstance('instance-previously-active') - .addActiveInstance('instance-currently-active') - .addActiveInstance('instance-previously-active') + .addActiveInstance('instance-currently-active', 'action group A') + .addActiveInstance('instance-previously-active', 'action group B') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-previously-active') - .addActiveInstance('instance-currently-active') + .addActiveInstance('instance-currently-active', 'action group A') .getEvents(); const eventsResult = { ...AlertInstanceSummaryFindEventsResult, @@ -144,16 +144,19 @@ describe('getAlertInstanceSummary()', () => { "id": "1", "instances": Object { "instance-currently-active": Object { + "actionGroupId": "action group A", "activeStartDate": "2019-02-12T21:01:22.479Z", "muted": false, "status": "Active", }, "instance-muted-no-activity": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "instance-previously-active": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts new file mode 100644 index 00000000000000..93aa3c38a04602 --- /dev/null +++ b/x-pack/plugins/alerts/server/config.test.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { configSchema } from './config'; + +describe('config validation', () => { + test('alerts defaults', () => { + const config: Record = {}; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "healthCheck": Object { + "interval": "60m", + }, + } + `); + }); +}); diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts new file mode 100644 index 00000000000000..a6d2196a407b5e --- /dev/null +++ b/x-pack/plugins/alerts/server/config.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 { schema, TypeOf } from '@kbn/config-schema'; +import { validateDurationSchema } from './lib'; + +export const configSchema = schema.object({ + healthCheck: schema.object({ + interval: schema.string({ validate: validateDurationSchema, defaultValue: '60m' }), + }), +}); + +export type AlertsConfig = TypeOf; diff --git a/x-pack/plugins/alerts/server/health/get_health.test.ts b/x-pack/plugins/alerts/server/health/get_health.test.ts new file mode 100644 index 00000000000000..34517a89f04d92 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_health.test.ts @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { savedObjectsRepositoryMock } from '../../../../../src/core/server/mocks'; +import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types'; +import { getHealth } from './get_health'; + +const savedObjectsRepository = savedObjectsRepositoryMock.create(); + +describe('getHealth()', () => { + test('return true if some of alerts has a decryption error', async () => { + const lastExecutionDateError = new Date().toISOString(); + const lastExecutionDate = new Date().toISOString(); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'error', + lastExecutionDate: lastExecutionDateError, + error: { + reason: AlertExecutionStatusErrorReasons.Decrypt, + message: 'Failed decrypt', + }, + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + ], + }); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '1s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [], + executionStatus: { + status: 'ok', + lastExecutionDate, + }, + }, + score: 1, + references: [], + }, + ], + }); + const result = await getHealth(savedObjectsRepository); + expect(result).toStrictEqual({ + executionHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + decryptionHealth: { + status: HealthStatus.Warning, + timestamp: lastExecutionDateError, + }, + }); + expect(savedObjectsRepository.find).toHaveBeenCalledTimes(4); + }); + + test('return false if no alerts with a decryption error', async () => { + const lastExecutionDateError = new Date().toISOString(); + const lastExecutionDate = new Date().toISOString(); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '1', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + executionStatus: { + status: 'error', + lastExecutionDate: lastExecutionDateError, + error: { + reason: AlertExecutionStatusErrorReasons.Execute, + message: 'Failed', + }, + }, + }, + score: 1, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + ], + }); + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 0, + per_page: 10, + page: 1, + saved_objects: [], + }); + + savedObjectsRepository.find.mockResolvedValueOnce({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: '2', + type: 'alert', + attributes: { + alertTypeId: 'myType', + schedule: { interval: '1s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + actions: [], + executionStatus: { + status: 'ok', + lastExecutionDate, + }, + }, + score: 1, + references: [], + }, + ], + }); + const result = await getHealth(savedObjectsRepository); + expect(result).toStrictEqual({ + executionHealth: { + status: HealthStatus.Warning, + timestamp: lastExecutionDateError, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + decryptionHealth: { + status: HealthStatus.OK, + timestamp: lastExecutionDate, + }, + }); + }); +}); diff --git a/x-pack/plugins/alerts/server/health/get_health.ts b/x-pack/plugins/alerts/server/health/get_health.ts new file mode 100644 index 00000000000000..b7b4582aa8d109 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_health.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ISavedObjectsRepository } from 'src/core/server'; +import { AlertsHealth, HealthStatus, RawAlert, AlertExecutionStatusErrorReasons } from '../types'; + +export const getHealth = async ( + internalSavedObjectsRepository: ISavedObjectsRepository +): Promise => { + const healthStatuses = { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + readHealth: { + status: HealthStatus.OK, + timestamp: '', + }, + }; + + const { saved_objects: decryptErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Decrypt}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (decryptErrorData.length > 0) { + healthStatuses.decryptionHealth = { + status: HealthStatus.Warning, + timestamp: decryptErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: executeErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Execute}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (executeErrorData.length > 0) { + healthStatuses.executionHealth = { + status: HealthStatus.Warning, + timestamp: executeErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: readErrorData } = await internalSavedObjectsRepository.find({ + filter: `alert.attributes.executionStatus.status:error and alert.attributes.executionStatus.error.reason:${AlertExecutionStatusErrorReasons.Read}`, + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + page: 1, + perPage: 1, + }); + + if (readErrorData.length > 0) { + healthStatuses.readHealth = { + status: HealthStatus.Warning, + timestamp: readErrorData[0].attributes.executionStatus.lastExecutionDate, + }; + } + + const { saved_objects: noErrorData } = await internalSavedObjectsRepository.find({ + filter: 'not alert.attributes.executionStatus.status:error', + fields: ['executionStatus'], + type: 'alert', + sortField: 'executionStatus.lastExecutionDate', + sortOrder: 'desc', + }); + const lastExecutionDate = + noErrorData.length > 0 + ? noErrorData[0].attributes.executionStatus.lastExecutionDate + : new Date().toISOString(); + + for (const [, statusItem] of Object.entries(healthStatuses)) { + if (statusItem.status === HealthStatus.OK) { + statusItem.timestamp = lastExecutionDate; + } + } + + return healthStatuses; +}; diff --git a/x-pack/plugins/alerts/server/health/get_state.test.ts b/x-pack/plugins/alerts/server/health/get_state.test.ts new file mode 100644 index 00000000000000..86981c486da0f5 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_state.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { taskManagerMock } from '../../../task_manager/server/mocks'; +import { getHealthStatusStream } from '.'; +import { TaskStatus } from '../../../task_manager/server'; +import { HealthStatus } from '../types'; + +describe('getHealthStatusStream()', () => { + const mockTaskManager = taskManagerMock.createStart(); + + it('should return an object with the "unavailable" level and proper summary of "Alerting framework is unhealthy"', async () => { + mockTaskManager.get.mockReturnValue( + new Promise((_resolve, _reject) => { + return { + id: 'test', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + runs: 1, + health_status: HealthStatus.Warning, + }, + taskType: 'alerting:alerting_health_check', + params: { + alertId: '1', + }, + ownerId: null, + }; + }) + ); + getHealthStatusStream(mockTaskManager).subscribe( + (val: { level: Readonly; summary: string }) => { + expect(val.level).toBe(false); + } + ); + }); + + it('should return an object with the "available" level and proper summary of "Alerting framework is healthy"', async () => { + mockTaskManager.get.mockReturnValue( + new Promise((_resolve, _reject) => { + return { + id: 'test', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + runs: 1, + health_status: HealthStatus.OK, + }, + taskType: 'alerting:alerting_health_check', + params: { + alertId: '1', + }, + ownerId: null, + }; + }) + ); + getHealthStatusStream(mockTaskManager).subscribe( + (val: { level: Readonly; summary: string }) => { + expect(val.level).toBe(true); + } + ); + }); +}); diff --git a/x-pack/plugins/alerts/server/health/get_state.ts b/x-pack/plugins/alerts/server/health/get_state.ts new file mode 100644 index 00000000000000..476456ecad88ae --- /dev/null +++ b/x-pack/plugins/alerts/server/health/get_state.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { interval, Observable } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; +import { ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server'; +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { HEALTH_TASK_ID } from './task'; +import { HealthStatus } from '../types'; + +async function getLatestTaskState(taskManager: TaskManagerStartContract) { + try { + const result = await taskManager.get(HEALTH_TASK_ID); + return result; + } catch (err) { + const errMessage = err && err.message ? err.message : err.toString(); + if (!errMessage.includes('NotInitialized')) { + throw err; + } + } + + return null; +} + +const LEVEL_SUMMARY = { + [ServiceStatusLevels.available.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.available', + { + defaultMessage: 'Alerting framework is available', + } + ), + [ServiceStatusLevels.degraded.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.degraded', + { + defaultMessage: 'Alerting framework is degraded', + } + ), + [ServiceStatusLevels.unavailable.toString()]: i18n.translate( + 'xpack.alerts.server.healthStatus.unavailable', + { + defaultMessage: 'Alerting framework is unavailable', + } + ), +}; + +export const getHealthStatusStream = ( + taskManager: TaskManagerStartContract +): Observable> => { + return interval(60000 * 5).pipe( + switchMap(async () => { + const doc = await getLatestTaskState(taskManager); + const level = + doc?.state?.health_status === HealthStatus.OK + ? ServiceStatusLevels.available + : doc?.state?.health_status === HealthStatus.Warning + ? ServiceStatusLevels.degraded + : ServiceStatusLevels.unavailable; + return { + level, + summary: LEVEL_SUMMARY[level.toString()], + }; + }), + catchError(async (error) => ({ + level: ServiceStatusLevels.unavailable, + summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()], + meta: { error }, + })) + ); +}; diff --git a/x-pack/plugins/alerts/server/health/index.ts b/x-pack/plugins/alerts/server/health/index.ts new file mode 100644 index 00000000000000..730c4596aa550e --- /dev/null +++ b/x-pack/plugins/alerts/server/health/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 { getHealthStatusStream } from './get_state'; +export { scheduleAlertingHealthCheck, initializeAlertingHealth } from './task'; diff --git a/x-pack/plugins/alerts/server/health/task.ts b/x-pack/plugins/alerts/server/health/task.ts new file mode 100644 index 00000000000000..6ea01a1083c130 --- /dev/null +++ b/x-pack/plugins/alerts/server/health/task.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, Logger } from 'kibana/server'; +import { + RunContext, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../task_manager/server'; +import { AlertsConfig } from '../config'; +import { AlertingPluginsStart } from '../plugin'; +import { HealthStatus } from '../types'; +import { getHealth } from './get_health'; + +export const HEALTH_TASK_TYPE = 'alerting_health_check'; + +export const HEALTH_TASK_ID = `Alerting-${HEALTH_TASK_TYPE}`; + +export function initializeAlertingHealth( + logger: Logger, + taskManager: TaskManagerSetupContract, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + registerAlertingHealthCheckTask(logger, taskManager, coreStartServices); +} + +export async function scheduleAlertingHealthCheck( + logger: Logger, + config: Promise, + taskManager: TaskManagerStartContract +) { + try { + const interval = (await config).healthCheck.interval; + await taskManager.ensureScheduled({ + id: HEALTH_TASK_ID, + taskType: HEALTH_TASK_TYPE, + schedule: { + interval, + }, + state: {}, + params: {}, + }); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } +} + +function registerAlertingHealthCheckTask( + logger: Logger, + taskManager: TaskManagerSetupContract, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + taskManager.registerTaskDefinitions({ + [HEALTH_TASK_TYPE]: { + title: 'Alerting framework health check task', + createTaskRunner: healthCheckTaskRunner(logger, coreStartServices), + }, + }); +} + +export function healthCheckTaskRunner( + logger: Logger, + coreStartServices: Promise<[CoreStart, AlertingPluginsStart, unknown]> +) { + return ({ taskInstance }: RunContext) => { + const { state } = taskInstance; + return { + async run() { + try { + const alertingHealthStatus = await getHealth( + (await coreStartServices)[0].savedObjects.createInternalRepository(['alert']) + ); + return { + state: { + runs: (state.runs || 0) + 1, + health_status: alertingHealthStatus.decryptionHealth.status, + }, + }; + } catch (errMsg) { + logger.warn(`Error executing alerting health check task: ${errMsg}`); + return { + state: { + runs: (state.runs || 0) + 1, + health_status: HealthStatus.Error, + }, + }; + } + }, + }; + }; +} diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts index 1e442c5196cf20..64e585da5c6544 100644 --- a/x-pack/plugins/alerts/server/index.ts +++ b/x-pack/plugins/alerts/server/index.ts @@ -5,8 +5,10 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertsClient as AlertsClientClass } from './alerts_client'; -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; import { AlertingPlugin } from './plugin'; +import { configSchema } from './config'; +import { AlertsConfigType } from './types'; export type AlertsClient = PublicMethodsOf; @@ -30,3 +32,7 @@ export { AlertInstance } from './alert_instance'; export { parseDuration } from './lib'; export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext); + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts index 3372d19cd40901..bb24ab034d0dde 100644 --- a/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts @@ -57,7 +57,9 @@ describe('AlertExecutionStatus', () => { }); test('error with a reason', () => { - const status = executionStatusFromError(new ErrorWithReason('execute', new Error('hoo!'))); + const status = executionStatusFromError( + new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, new Error('hoo!')) + ); expect(status.status).toBe('error'); expect(status.error).toMatchInlineSnapshot(` Object { @@ -71,7 +73,7 @@ describe('AlertExecutionStatus', () => { describe('alertExecutionStatusToRaw()', () => { const date = new Date('2020-09-03T16:26:58Z'); const status = 'ok'; - const reason: AlertExecutionStatusErrorReasons = 'decrypt'; + const reason = AlertExecutionStatusErrorReasons.Decrypt; const error = { reason, message: 'wops' }; test('status without an error', () => { @@ -102,7 +104,7 @@ describe('AlertExecutionStatus', () => { describe('alertExecutionStatusFromRaw()', () => { const date = new Date('2020-09-03T16:26:58Z').toISOString(); const status = 'active'; - const reason: AlertExecutionStatusErrorReasons = 'execute'; + const reason = AlertExecutionStatusErrorReasons.Execute; const error = { reason, message: 'wops' }; test('no input', () => { diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 566a1770c0658e..f9e4a2908d6ce3 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -104,11 +104,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -184,7 +186,7 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-1') @@ -202,6 +204,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -218,7 +221,7 @@ describe('alertInstanceSummaryFromEventLog', () => { const eventsFactory = new EventsFactory(); const events = eventsFactory .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() .addResolvedInstance('instance-1') @@ -236,6 +239,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -253,10 +257,10 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -271,6 +275,79 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", + "activeStartDate": "2020-06-18T00:00:00.000Z", + "muted": false, + "status": "Active", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "Active", + } + `); + }); + + test('alert with currently active instance with no action group in event log', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', undefined) + .advanceTime(10000) + .addExecute() + .addActiveInstance('instance-1', undefined) + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": undefined, + "activeStartDate": "2020-06-18T00:00:00.000Z", + "muted": false, + "status": "Active", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "Active", + } + `); + }); + + test('alert with currently active instance that switched action groups', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') + .advanceTime(10000) + .addExecute() + .addActiveInstance('instance-1', 'action group B') + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": "action group B", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", @@ -287,10 +364,10 @@ describe('alertInstanceSummaryFromEventLog', () => { const eventsFactory = new EventsFactory(); const events = eventsFactory .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -305,6 +382,7 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", "activeStartDate": undefined, "muted": false, "status": "Active", @@ -322,12 +400,12 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addNewInstance('instance-2') - .addActiveInstance('instance-2') + .addActiveInstance('instance-2', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addResolvedInstance('instance-2') .getEvents(); @@ -343,11 +421,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group A", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": true, "status": "Active", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": true, "status": "OK", @@ -365,19 +445,19 @@ describe('alertInstanceSummaryFromEventLog', () => { const events = eventsFactory .addExecute() .addNewInstance('instance-1') - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addNewInstance('instance-2') - .addActiveInstance('instance-2') + .addActiveInstance('instance-2', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') .addResolvedInstance('instance-2') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group B') .advanceTime(10000) .addExecute() - .addActiveInstance('instance-1') + .addActiveInstance('instance-1', 'action group B') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -392,11 +472,13 @@ describe('alertInstanceSummaryFromEventLog', () => { Object { "instances": Object { "instance-1": Object { + "actionGroupId": "action group B", "activeStartDate": "2020-06-18T00:00:00.000Z", "muted": false, "status": "Active", }, "instance-2": Object { + "actionGroupId": undefined, "activeStartDate": undefined, "muted": false, "status": "OK", @@ -452,14 +534,17 @@ export class EventsFactory { return this; } - addActiveInstance(instanceId: string): EventsFactory { + addActiveInstance(instanceId: string, actionGroupId: string | undefined): EventsFactory { + const kibanaAlerting = actionGroupId + ? { instance_id: instanceId, action_group_id: actionGroupId } + : { instance_id: instanceId }; this.events.push({ '@timestamp': this.date, event: { provider: EVENT_LOG_PROVIDER, action: EVENT_LOG_ACTIONS.activeInstance, }, - kibana: { alerting: { instance_id: instanceId } }, + kibana: { alerting: kibanaAlerting }, }); return this; } diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 9a5e870c8199a2..8fed97a74435dc 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -78,10 +78,12 @@ export function alertInstanceSummaryFromEventLog( // intentionally no break here case EVENT_LOG_ACTIONS.activeInstance: status.status = 'Active'; + status.actionGroupId = event?.kibana?.alerting?.action_group_id; break; case EVENT_LOG_ACTIONS.resolvedInstance: status.status = 'OK'; status.activeStartDate = undefined; + status.actionGroupId = undefined; } } @@ -118,6 +120,7 @@ function getAlertInstanceStatus( const status: AlertInstanceStatus = { status: 'OK', muted: false, + actionGroupId: undefined, activeStartDate: undefined, }; instances.set(instanceId, status); diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts index f31f5844003086..eff935966345f9 100644 --- a/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts +++ b/x-pack/plugins/alerts/server/lib/error_with_reason.test.ts @@ -5,20 +5,21 @@ */ import { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; +import { AlertExecutionStatusErrorReasons } from '../types'; describe('ErrorWithReason', () => { const plainError = new Error('well, actually'); - const errorWithReason = new ErrorWithReason('decrypt', plainError); + const errorWithReason = new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, plainError); test('ErrorWithReason class', () => { expect(errorWithReason.message).toBe(plainError.message); expect(errorWithReason.error).toBe(plainError); - expect(errorWithReason.reason).toBe('decrypt'); + expect(errorWithReason.reason).toBe(AlertExecutionStatusErrorReasons.Decrypt); }); test('getReasonFromError()', () => { expect(getReasonFromError(plainError)).toBe('unknown'); - expect(getReasonFromError(errorWithReason)).toBe('decrypt'); + expect(getReasonFromError(errorWithReason)).toBe(AlertExecutionStatusErrorReasons.Decrypt); }); test('isErrorWithReason()', () => { diff --git a/x-pack/plugins/alerts/server/lib/error_with_reason.ts b/x-pack/plugins/alerts/server/lib/error_with_reason.ts index 29eb666e644272..a732b44ef2238c 100644 --- a/x-pack/plugins/alerts/server/lib/error_with_reason.ts +++ b/x-pack/plugins/alerts/server/lib/error_with_reason.ts @@ -21,7 +21,7 @@ export function getReasonFromError(error: Error): AlertExecutionStatusErrorReaso if (isErrorWithReason(error)) { return error.reason; } - return 'unknown'; + return AlertExecutionStatusErrorReasons.Unknown; } export function isErrorWithReason(error: Error | ErrorWithReason): error is ErrorWithReason { diff --git a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts index b570957d82de4a..ab21dc77fa251c 100644 --- a/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts +++ b/x-pack/plugins/alerts/server/lib/is_alert_not_found_error.test.ts @@ -8,6 +8,7 @@ import { isAlertSavedObjectNotFoundError } from './is_alert_not_found_error'; import { ErrorWithReason } from './error_with_reason'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import uuid from 'uuid'; +import { AlertExecutionStatusErrorReasons } from '../types'; describe('isAlertSavedObjectNotFoundError', () => { const id = uuid.v4(); @@ -25,7 +26,7 @@ describe('isAlertSavedObjectNotFoundError', () => { }); test('identifies SavedObjects Not Found errors wrapped in an ErrorWithReason', () => { - const error = new ErrorWithReason('read', errorSONF); + const error = new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, errorSONF); expect(isAlertSavedObjectNotFoundError(error, id)).toBe(true); }); }); diff --git a/x-pack/plugins/alerts/server/mocks.ts b/x-pack/plugins/alerts/server/mocks.ts index 05d64bdbb77f4f..cfae4c650bd42c 100644 --- a/x-pack/plugins/alerts/server/mocks.ts +++ b/x-pack/plugins/alerts/server/mocks.ts @@ -25,6 +25,7 @@ const createStartMock = () => { const mock: jest.Mocked = { listTypes: jest.fn(), getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()), + getFrameworkHealth: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index b13a1c62f66022..715fbc6aeed459 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -5,7 +5,7 @@ */ import { AlertingPlugin, AlertingPluginsSetup, AlertingPluginsStart } from './plugin'; -import { coreMock } from '../../../../src/core/server/mocks'; +import { coreMock, statusServiceMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; @@ -13,15 +13,21 @@ import { eventLogServiceMock } from '../../event_log/server/event_log_service.mo import { KibanaRequest, CoreSetup } from 'kibana/server'; import { featuresPluginMock } from '../../features/server/mocks'; import { KibanaFeature } from '../../features/server'; +import { AlertsConfig } from './config'; describe('Alerting Plugin', () => { describe('setup()', () => { it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + const statusMock = statusServiceMock.createSetupContract(); await plugin.setup( ({ ...coreSetup, @@ -29,6 +35,7 @@ describe('Alerting Plugin', () => { ...coreSetup.http, route: jest.fn(), }, + status: statusMock, } as unknown) as CoreSetup, ({ licensing: licensingMock.createSetup(), @@ -38,6 +45,7 @@ describe('Alerting Plugin', () => { } as unknown) as AlertingPluginsSetup ); + expect(statusMock.set).toHaveBeenCalledTimes(1); expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); expect(context.logger.get().warn).toHaveBeenCalledWith( 'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.' @@ -55,7 +63,11 @@ describe('Alerting Plugin', () => { */ describe('getAlertsClientWithRequest()', () => { it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); @@ -98,7 +110,11 @@ describe('Alerting Plugin', () => { }); it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + }); const plugin = new AlertingPlugin(context); const coreSetup = coreMock.createSetup(); diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 75873a2845c15e..1fa89606a76fc7 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -6,6 +6,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { first, map } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { combineLatest } from 'rxjs'; import { SecurityPluginSetup } from '../../security/server'; import { EncryptedSavedObjectsPluginSetup, @@ -30,6 +31,8 @@ import { SharedGlobalConfig, ElasticsearchServiceStart, ILegacyClusterClient, + StatusServiceSetup, + ServiceStatus, } from '../../../../src/core/server'; import { @@ -56,12 +59,19 @@ import { PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, } from '../../actions/server'; -import { Services } from './types'; +import { AlertsHealth, Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; import { IEventLogger, IEventLogService, IEventLogClientService } from '../../event_log/server'; import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; import { setupSavedObjects } from './saved_objects'; +import { + getHealthStatusStream, + scheduleAlertingHealthCheck, + initializeAlertingHealth, +} from './health'; +import { AlertsConfig } from './config'; +import { getHealth } from './health/get_health'; export const EVENT_LOG_PROVIDER = 'alerting'; export const EVENT_LOG_ACTIONS = { @@ -78,6 +88,7 @@ export interface PluginSetupContract { export interface PluginStartContract { listTypes: AlertTypeRegistry['list']; getAlertsClientWithRequest(request: KibanaRequest): PublicMethodsOf; + getFrameworkHealth: () => Promise; } export interface AlertingPluginsSetup { @@ -89,6 +100,7 @@ export interface AlertingPluginsSetup { spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; eventLog: IEventLogService; + statusService: StatusServiceSetup; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -99,6 +111,7 @@ export interface AlertingPluginsStart { } export class AlertingPlugin { + private readonly config: Promise; private readonly logger: Logger; private alertTypeRegistry?: AlertTypeRegistry; private readonly taskRunnerFactory: TaskRunnerFactory; @@ -115,6 +128,7 @@ export class AlertingPlugin { private eventLogger?: IEventLogger; constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.create().pipe(first()).toPromise(); this.logger = initializerContext.logger.get('plugins', 'alerting'); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); @@ -186,6 +200,25 @@ export class AlertingPlugin { }); } + core.getStartServices().then(async ([, startPlugins]) => { + core.status.set( + combineLatest([ + core.status.derivedStatus$, + getHealthStatusStream(startPlugins.taskManager), + ]).pipe( + map(([derivedStatus, healthStatus]) => { + if (healthStatus.level > derivedStatus.level) { + return healthStatus as ServiceStatus; + } else { + return derivedStatus; + } + }) + ) + ); + }); + + initializeAlertingHealth(this.logger, plugins.taskManager, core.getStartServices()); + core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext(core)); // Routes @@ -275,10 +308,13 @@ export class AlertingPlugin { }); scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); + scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager); return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), getAlertsClientWithRequest, + getFrameworkHealth: async () => + await getHealth(core.savedObjects.createInternalRepository(['alert'])), }; } @@ -293,6 +329,8 @@ export class AlertingPlugin { return alertsClientFactory!.create(request, savedObjects); }, listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!), + getFrameworkHealth: async () => + await getHealth(savedObjects.createInternalRepository(['alert'])), }; }; }; diff --git a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts index 3d13fc65ab260e..b3f407b20c142c 100644 --- a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts @@ -14,7 +14,7 @@ import { identity } from 'lodash'; import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from '../../../../../src/core/server/mocks'; import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; -import { AlertType } from '../../common'; +import { AlertsHealth, AlertType } from '../../common'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; export function mockHandlerArguments( @@ -22,10 +22,13 @@ export function mockHandlerArguments( alertsClient = alertsClientMock.create(), listTypes: listTypesRes = [], esClient = elasticsearchServiceMock.createLegacyClusterClient(), + getFrameworkHealth, }: { alertsClient?: AlertsClientMock; listTypes?: AlertType[]; esClient?: jest.Mocked; + getFrameworkHealth?: jest.MockInstance, []> & + (() => Promise); }, req: unknown, res?: Array> @@ -39,6 +42,7 @@ export function mockHandlerArguments( getAlertsClient() { return alertsClient || alertsClientMock.create(); }, + getFrameworkHealth, }, } as unknown) as RequestHandlerContext, req as KibanaRequest, diff --git a/x-pack/plugins/alerts/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts index ce782dbd631a5a..d1967c6dd9bf84 100644 --- a/x-pack/plugins/alerts/server/routes/health.test.ts +++ b/x-pack/plugins/alerts/server/routes/health.test.ts @@ -11,13 +11,34 @@ import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockLicenseState } from '../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; +import { alertsClientMock } from '../alerts_client.mock'; +import { HealthStatus } from '../types'; +import { alertsMock } from '../mocks'; +const alertsClient = alertsClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), })); +const alerting = alertsMock.createStart(); + +const currentDate = new Date().toISOString(); beforeEach(() => { jest.resetAllMocks(); + alerting.getFrameworkHealth.mockResolvedValue({ + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + }); }); describe('healthRoute', () => { @@ -46,7 +67,7 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient, alertsClient }, {}, ['ok']); await handler(context, req, res); @@ -75,16 +96,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": false, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: false, + isSufficientlySecure: true, + }, + }); }); it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { @@ -99,16 +136,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); it('evaluates missing security http info from the usage api to mean that the security plugin is disbled', async () => { @@ -123,16 +176,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: {} })); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); it('evaluates security enabled, and missing ssl info from the usage api to mean that the user cannot generate keys', async () => { @@ -147,16 +216,32 @@ describe('healthRoute', () => { const esClient = elasticsearchServiceMock.createLegacyClusterClient(); esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: { enabled: true } })); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": false, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); }); it('evaluates security enabled, SSL info present but missing http info from the usage api to mean that the user cannot generate keys', async () => { @@ -173,16 +258,32 @@ describe('healthRoute', () => { Promise.resolve({ security: { enabled: true, ssl: {} } }) ); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": false, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: false, + }, + }); }); it('evaluates security and tls enabled to mean that the user can generate keys', async () => { @@ -199,15 +300,31 @@ describe('healthRoute', () => { Promise.resolve({ security: { enabled: true, ssl: { http: { enabled: true } } } }) ); - const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments( + { esClient, alertsClient, getFrameworkHealth: alerting.getFrameworkHealth }, + {}, + ['ok'] + ); - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "hasPermanentEncryptionKey": true, - "isSufficientlySecure": true, + expect(await handler(context, req, res)).toStrictEqual({ + body: { + alertingFrameworkHeath: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, + readHealth: { + status: HealthStatus.OK, + timestamp: currentDate, + }, }, - } - `); + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }, + }); }); }); diff --git a/x-pack/plugins/alerts/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts index b66e28b24e8a74..bfd5b1e2722878 100644 --- a/x-pack/plugins/alerts/server/routes/health.ts +++ b/x-pack/plugins/alerts/server/routes/health.ts @@ -43,6 +43,9 @@ export function healthRoute( res: KibanaResponseFactory ): Promise { verifyApiAccess(licenseState); + if (!context.alerting) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for alerting' }); + } try { const { security: { @@ -57,9 +60,12 @@ export function healthRoute( path: '/_xpack/usage', }); + const alertingFrameworkHeath = await context.alerting.getFrameworkHealth(); + const frameworkHealth: AlertingFrameworkHealth = { isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey, + alertingFrameworkHeath, }; return res.ok({ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 86e78dea66a095..4d0d69010914e2 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -292,6 +292,7 @@ describe('Task Runner', () => { kibana: { alerting: { instance_id: '1', + action_group_id: 'default', }, saved_objects: [ { @@ -302,7 +303,7 @@ describe('Task Runner', () => { }, ], }, - message: "test:1: 'alert-name' active instance: '1'", + message: "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }); expect(eventLogger.logEvent).toHaveBeenCalledWith({ event: { @@ -424,6 +425,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": undefined, "instance_id": "1", }, "saved_objects": Array [ @@ -445,6 +447,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": "default", "instance_id": "1", }, "saved_objects": Array [ @@ -456,7 +459,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' active instance: '1'", + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], Array [ @@ -565,6 +568,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": undefined, "instance_id": "2", }, "saved_objects": Array [ @@ -586,6 +590,7 @@ describe('Task Runner', () => { }, "kibana": Object { "alerting": Object { + "action_group_id": "default", "instance_id": "1", }, "saved_objects": Array [ @@ -597,7 +602,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' active instance: '1'", + "message": "test:1: 'alert-name' active instance: '1' in actionGroup: 'default'", }, ], ] diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 2611ba766173ba..86bf7006e8d09e 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { pickBy, mapValues, without } from 'lodash'; +import { Dictionary, pickBy, mapValues, without } from 'lodash'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance, throwUnrecoverableError } from '../../../task_manager/server'; @@ -28,6 +28,7 @@ import { AlertExecutorOptions, SanitizedAlert, AlertExecutionStatus, + AlertExecutionStatusErrorReasons, } from '../types'; import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type'; import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; @@ -211,7 +212,7 @@ export class TaskRunner { event.event = event.event || {}; event.event.outcome = 'failure'; eventLogger.logEvent(event); - throw new ErrorWithReason('execute', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Execute, err); } eventLogger.stopTiming(event); @@ -224,11 +225,10 @@ export class TaskRunner { const instancesWithScheduledActions = pickBy(alertInstances, (alertInstance: AlertInstance) => alertInstance.hasScheduledActions() ); - const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions); generateNewAndResolvedInstanceEvents({ eventLogger, originalAlertInstanceIds, - currentAlertInstanceIds, + currentAlertInstances: instancesWithScheduledActions, alertId, alertLabel, namespace, @@ -289,7 +289,7 @@ export class TaskRunner { try { apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId); } catch (err) { - throw new ErrorWithReason('decrypt', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Decrypt, err); } const [services, alertsClient] = this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); @@ -299,7 +299,7 @@ export class TaskRunner { try { alert = await alertsClient.get({ id: alertId }); } catch (err) { - throw new ErrorWithReason('read', err); + throw new ErrorWithReason(AlertExecutionStatusErrorReasons.Read, err); } return { @@ -382,7 +382,7 @@ export class TaskRunner { interface GenerateNewAndResolvedInstanceEventsParams { eventLogger: IEventLogger; originalAlertInstanceIds: string[]; - currentAlertInstanceIds: string[]; + currentAlertInstances: Dictionary; alertId: string; alertLabel: string; namespace: string | undefined; @@ -393,9 +393,10 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst eventLogger, alertId, namespace, - currentAlertInstanceIds, + currentAlertInstances, originalAlertInstanceIds, } = params; + const currentAlertInstanceIds = Object.keys(currentAlertInstances); const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); @@ -411,11 +412,12 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst } for (const id of currentAlertInstanceIds) { - const message = `${params.alertLabel} active instance: '${id}'`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message); + const actionGroup = currentAlertInstances[id].getScheduledActionOptions()?.actionGroup; + const message = `${params.alertLabel} active instance: '${id}' in actionGroup: '${actionGroup}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.activeInstance, message, actionGroup); } - function logInstanceEvent(instanceId: string, action: string, message: string) { + function logInstanceEvent(instanceId: string, action: string, message: string, group?: string) { const event: IEvent = { event: { action, @@ -423,6 +425,7 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst kibana: { alerting: { instance_id: instanceId, + action_group_id: group, }, saved_objects: [ { diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 42eef9bba10e52..9226461f6e30a4 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -27,6 +27,7 @@ import { AlertInstanceState, AlertExecutionStatuses, AlertExecutionStatusErrorReasons, + AlertsHealth, } from '../common'; export type WithoutQueryAndParams = Pick>; @@ -39,6 +40,7 @@ declare module 'src/core/server' { alerting?: { getAlertsClient: () => AlertsClient; listTypes: AlertTypeRegistry['list']; + getFrameworkHealth: () => Promise; }; } } @@ -172,4 +174,10 @@ export interface AlertingPlugin { start: PluginStartContract; } +export interface AlertsConfigType { + healthCheck: { + interval: string; + }; +} + export type AlertTypeRegistry = PublicMethodsOf; diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js index 72b49bb85b7a59..0ecda7a113de75 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js +++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js @@ -1,3 +1,3 @@ module.exports = { - __version: '5.5.0', -}; + "__version": "5.4.0" +} diff --git a/x-pack/plugins/apm/e2e/package.json b/x-pack/plugins/apm/e2e/package.json deleted file mode 100644 index 5839f4d58537c0..00000000000000 --- a/x-pack/plugins/apm/e2e/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "apm-cypress", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "cypress:open": "../../../../node_modules/.bin/cypress open", - "cypress:run": "../../../../node_modules/.bin/cypress run --spec **/*.feature" - } -} \ No newline at end of file diff --git a/x-pack/plugins/apm/e2e/run-e2e.sh b/x-pack/plugins/apm/e2e/run-e2e.sh index 6cdae93aec63be..85ab67bbf9a10d 100755 --- a/x-pack/plugins/apm/e2e/run-e2e.sh +++ b/x-pack/plugins/apm/e2e/run-e2e.sh @@ -20,6 +20,8 @@ normal=$(tput sgr0) E2E_DIR="${0%/*}" TMP_DIR="tmp" APM_IT_DIR="tmp/apm-integration-testing" +WAIT_ON_BIN="../../../../node_modules/.bin/wait-on" +CYPRESS_BIN="../../../../node_modules/.bin/cypress" cd ${E2E_DIR} @@ -92,14 +94,6 @@ if [ $? -ne 0 ]; then exit 1 fi -# -# Cypress -################################################## -echo "" # newline -echo "${bold}Cypress (logs: ${E2E_DIR}${TMP_DIR}/e2e-yarn.log)${normal}" -echo "Installing cypress dependencies " -yarn &> ${TMP_DIR}/e2e-yarn.log - # # Static mock data ################################################## @@ -148,7 +142,7 @@ fi echo "" # newline echo "${bold}Waiting for Kibana to start...${normal}" echo "Note: you need to start Kibana manually. Find the instructions at the top." -yarn wait-on -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null +$WAIT_ON_BIN -i 500 -w 500 http-get://admin:changeme@localhost:$KIBANA_PORT/api/status > /dev/null ## Workaround to wait for the http server running ## See: https://github.com/elastic/kibana/issues/66326 @@ -165,7 +159,7 @@ echo "✅ Setup completed successfully. Running tests..." # # run cypress tests ################################################## -yarn cypress run --config pageLoadTimeout=100000,watchForFileChanges=true +$CYPRESS_BIN run --config pageLoadTimeout=100000,watchForFileChanges=true e2e_status=$? # @@ -173,7 +167,7 @@ e2e_status=$? ################################################## echo "${bold}If you want to run the test interactively, run:${normal}" echo "" # newline -echo "cd ${E2E_DIR} && yarn cypress open --config pageLoadTimeout=100000,watchForFileChanges=true" +echo "cd ${E2E_DIR} && ${CYPRESS_BIN} open --config pageLoadTimeout=100000,watchForFileChanges=true" # Report the e2e status at the very end if [ $e2e_status -ne 0 ]; then diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 090110b0454c03..29a0d1fdf42494 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -43,6 +43,7 @@ export const config = { ), telemetryCollectionEnabled: schema.boolean({ defaultValue: true }), metricsInterval: schema.number({ defaultValue: 30 }), + maxServiceEnvironments: schema.number({ defaultValue: 100 }), }), }; @@ -74,6 +75,7 @@ export function mergeConfigs( 'xpack.apm.serviceMapMaxTracesPerRequest': apmConfig.serviceMapMaxTracesPerRequest, 'xpack.apm.ui.enabled': apmConfig.ui.enabled, + 'xpack.apm.maxServiceEnvironments': apmConfig.maxServiceEnvironments, 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems, 'xpack.apm.ui.transactionGroupBucketSize': apmConfig.ui.transactionGroupBucketSize, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index 7b63f2c354916c..ecda5b0e8504bf 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -66,6 +66,7 @@ export function registerErrorCountAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.errorIndices'], @@ -100,6 +101,7 @@ export function registerErrorCountAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 1d8b664751ba29..d9e69c8f3b7d7c 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -75,6 +75,7 @@ export function registerTransactionDurationAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.transactionIndices'], @@ -112,6 +113,7 @@ export function registerTransactionDurationAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 969f4ceaca93a9..06b296db5a4853 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -71,6 +71,7 @@ export function registerTransactionErrorRateAlertType({ config, savedObjectsClient: services.savedObjectsClient, }); + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const searchParams = { index: indices['apm_oss.transactionIndices'], @@ -120,6 +121,7 @@ export function registerTransactionErrorRateAlertType({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 95ff357937d471..39b4f7a7fe81b5 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -24,7 +24,8 @@ export async function getAllEnvironments({ searchAggregatedTransactions: boolean; includeMissing?: boolean; }) { - const { apmEventClient } = setup; + const { apmEventClient, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; // omit filter for service.name if "All" option is selected const serviceNameFilter = serviceName @@ -55,7 +56,7 @@ export async function getAllEnvironments({ environments: { terms: { field: SERVICE_ENVIRONMENT, - size: 100, + size: maxServiceEnvironments, ...(!serviceName ? { min_doc_count: 0 } : {}), missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined, }, diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 3a38f80c87b35e..a6818f96c728ee 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -366,6 +366,7 @@ Array [ "environments": Object { "terms": Object { "field": "service.environment", + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index 7d190c5b664501..fac80cf22c310e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -337,7 +337,8 @@ export const getEnvironments = async ({ setup, projection, }: AggregationParams) => { - const { apmEventClient } = setup; + const { apmEventClient, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const response = await apmEventClient.search( mergeProjection(projection, { body: { @@ -352,6 +353,7 @@ export const getEnvironments = async ({ environments: { terms: { field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap index 8db97a4929eb05..18ef3f44331d98 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap @@ -127,7 +127,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ALL_OPTION_VALUE", - "size": 50, + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 8327ac59a95b2f..5e19f8f211cf70 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -18,7 +18,8 @@ export async function getExistingEnvironmentsForService({ serviceName: string | undefined; setup: Setup; }) { - const { internalClient, indices } = setup; + const { internalClient, indices, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; const bool = serviceName ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } @@ -34,7 +35,7 @@ export async function getExistingEnvironmentsForService({ terms: { field: SERVICE_ENVIRONMENT, missing: ALL_OPTION_VALUE, - size: 50, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap index d94b766aee6a89..3baaefe203ce75 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/ui_filters/__snapshots__/queries.test.ts.snap @@ -15,6 +15,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ENVIRONMENT_NOT_DEFINED", + "size": 100, }, }, }, @@ -58,6 +59,7 @@ Object { "terms": Object { "field": "service.environment", "missing": "ENVIRONMENT_NOT_DEFINED", + "size": 100, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts index e72cc7e2483ad4..b9f25e20f9f730 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -24,7 +24,7 @@ export async function getEnvironments({ serviceName?: string; searchAggregatedTransactions: boolean; }) { - const { start, end, apmEventClient } = setup; + const { start, end, apmEventClient, config } = setup; const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; @@ -34,6 +34,8 @@ export async function getEnvironments({ }); } + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + const params = { apm: { events: [ @@ -56,6 +58,7 @@ export async function getEnvironments({ terms: { field: SERVICE_ENVIRONMENT, missing: ENVIRONMENT_NOT_DEFINED.value, + size: maxServiceEnvironments, }, }, }, diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index 18b990b35b5a58..21b59dc516d060 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -76,6 +76,9 @@ export async function inspectSearchParams( case 'xpack.apm.metricsInterval': return 30; + + case 'xpack.apm.maxServiceEnvironments': + return 100; } }, } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot index 5ad49a207ed3ec..286c55994f27ec 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/dropdown_filter/component/__stories__/__snapshots__/dropdown_filter.stories.storyshot @@ -6,6 +6,7 @@ exports[`Storyshots renderers/DropdownFilter default 1`] = ` >
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx index c5fe7074fea0bf..07d749c5677dcf 100644 --- a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx +++ b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx @@ -6,6 +6,8 @@ import React, { useState, useEffect, useRef, FC, useCallback } from 'react'; +import { isEqual } from 'lodash'; + import { useNotifyService } from '../../services'; import { RenderToDom } from '../render_to_dom'; import { ErrorStrings } from '../../../i18n'; @@ -82,8 +84,12 @@ export const RenderWithFn: FC = ({ ); const render = useCallback(() => { + if (!isEqual(handlers.current, incomingHandlers)) { + handlers.current = incomingHandlers; + } + renderFn(renderTarget.current!, config, handlers.current); - }, [renderTarget, config, renderFn]); + }, [renderTarget, config, renderFn, incomingHandlers]); useEffect(() => { if (!domNode) { diff --git a/x-pack/plugins/canvas/shareable_runtime/api/index.ts b/x-pack/plugins/canvas/shareable_runtime/api/index.ts index 0780ab46cd873b..dc7445eb7bc5af 100644 --- a/x-pack/plugins/canvas/shareable_runtime/api/index.ts +++ b/x-pack/plugins/canvas/shareable_runtime/api/index.ts @@ -7,5 +7,7 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; import 'whatwg-fetch'; +import 'jquery'; +import '@kbn/ui-shared-deps/flot_charts'; export * from './shareable'; diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index 0a858969c4f6af..5c7eb50117d9b1 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -81,6 +81,10 @@ "instance_id": { "type": "keyword", "ignore_above": 1024 + }, + "action_group_id": { + "type": "keyword", + "ignore_above": 1024 } } }, diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 57fe90a8e876ed..3dbb43b15350f6 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -60,6 +60,7 @@ export const EventSchema = schema.maybe( alerting: schema.maybe( schema.object({ instance_id: ecsString(), + action_group_id: ecsString(), }) ), saved_objects: schema.maybe( diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index fd149d132031e1..c9af2b0aa57fb4 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -18,6 +18,10 @@ exports.EcsKibanaExtensionsMappings = { type: 'keyword', ignore_above: 1024, }, + action_group_id: { + type: 'keyword', + ignore_above: 1024, + }, }, }, // array of saved object references, for "linking" via search @@ -63,6 +67,7 @@ exports.EcsEventLogProperties = [ 'user.name', 'kibana.server_uuid', 'kibana.alerting.instance_id', + 'kibana.alerting.action_group_id', 'kibana.saved_objects.rel', 'kibana.saved_objects.namespace', 'kibana.saved_objects.id', diff --git a/x-pack/plugins/index_lifecycle_management/kibana.json b/x-pack/plugins/index_lifecycle_management/kibana.json index 1b0a73c6a01333..21e7e7888acb92 100644 --- a/x-pack/plugins/index_lifecycle_management/kibana.json +++ b/x-pack/plugins/index_lifecycle_management/kibana.json @@ -6,7 +6,8 @@ "requiredPlugins": [ "licensing", "management", - "features" + "features", + "share" ], "optionalPlugins": [ "cloud", diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index f7f8b30324bcad..856981fe5c4f93 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -13,6 +13,7 @@ import { UIM_APP_LOAD } from './constants/ui_metric'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; +import { ROUTES } from './services/navigation'; export const App = ({ history, @@ -28,14 +29,14 @@ export const App = ({ return ( - + } /> } /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx index 7b6521fd8a9ef8..09c81efe163b58 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -35,7 +35,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../index_management/public'; import { PolicyFromES } from '../../../../../common/types'; -import { getPolicyPath } from '../../../services/navigation'; +import { getPolicyEditPath } from '../../../services/navigation'; import { sortTable } from '../../../services'; import { trackUiMetric } from '../../../services/ui_metric'; @@ -229,7 +229,7 @@ export const TableContent: React.FunctionComponent = ({ return ( + {...reactRouterNavigate(history, getPolicyEditPath(value as string), () => trackUiMetric(METRIC_TYPE.CLICK, UIM_EDIT_CLICK) )} > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx index 0c396dae757831..55987bd9a62a99 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -26,6 +26,7 @@ import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_rea import { PolicyFromES } from '../../../../common/types'; import { filterItems } from '../../services'; import { TableContent } from './components/table_content'; +import { getPolicyCreatePath } from '../../services/navigation'; interface Props { policies: PolicyFromES[]; @@ -45,7 +46,7 @@ export const PolicyTable: React.FunctionComponent = ({ const createPolicyButton = ( { +export const ROUTES = { + list: '/policies', + edit: '/policies/edit/:policyName?', + create: '/policies/edit', +}; + +export const getPolicyEditPath = (policyName: string): string => { return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; + +export const getPolicyCreatePath = () => { + return ROUTES.create; +}; + +export const getPoliciesListPath = () => { + return ROUTES.list; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx index afdf726ea02f99..80c8e1414e1f8a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx @@ -25,7 +25,7 @@ import { } from '@elastic/eui'; import { ApplicationStart } from 'kibana/public'; -import { getPolicyPath } from '../../application/services/navigation'; +import { getPolicyEditPath } from '../../application/services/navigation'; import { Index, IndexLifecyclePolicy } from '../../../common/types'; const getHeaders = (): Array<[keyof IndexLifecyclePolicy, string]> => { @@ -192,7 +192,7 @@ export class IndexLifecycleSummary extends Component { content = ( {value} diff --git a/x-pack/plugins/index_lifecycle_management/public/index.ts b/x-pack/plugins/index_lifecycle_management/public/index.ts index 586763188a54b1..2aee76cd8b1367 100644 --- a/x-pack/plugins/index_lifecycle_management/public/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/index.ts @@ -12,3 +12,5 @@ import { IndexLifecycleManagementPlugin } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { return new IndexLifecycleManagementPlugin(initializerContext); }; + +export { ILM_URL_GENERATOR_ID, IlmUrlGeneratorState } from './url_generator'; diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 6300c6bfc7eb1d..deef5cfe6ef2c9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -14,14 +14,15 @@ import { init as initUiMetric } from './application/services/ui_metric'; import { init as initNotification } from './application/services/notification'; import { BreadcrumbService } from './application/services/breadcrumbs'; import { addAllExtensions } from './extend_index_management'; -import { PluginsDependencies, ClientConfigType } from './types'; +import { ClientConfigType, SetupDependencies } from './types'; +import { registerUrlGenerator } from './url_generator'; export class IndexLifecycleManagementPlugin { constructor(private readonly initializerContext: PluginInitializerContext) {} private breadcrumbService = new BreadcrumbService(); - public setup(coreSetup: CoreSetup, plugins: PluginsDependencies) { + public setup(coreSetup: CoreSetup, plugins: SetupDependencies) { const { ui: { enabled: isIndexLifecycleManagementUiEnabled }, } = this.initializerContext.config.get(); @@ -34,7 +35,7 @@ export class IndexLifecycleManagementPlugin { getStartServices, } = coreSetup; - const { usageCollection, management, indexManagement, home, cloud } = plugins; + const { usageCollection, management, indexManagement, home, cloud, share } = plugins; // Initialize services even if the app isn't mounted, because they're used by index management extensions. initHttp(http); @@ -102,6 +103,8 @@ export class IndexLifecycleManagementPlugin { if (indexManagement) { addAllExtensions(indexManagement.extensionsService); } + + registerUrlGenerator(coreSetup, management, share); } } diff --git a/x-pack/plugins/index_lifecycle_management/public/types.ts b/x-pack/plugins/index_lifecycle_management/public/types.ts index 6b11830b424af0..1ce43957b1444b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/types.ts @@ -9,15 +9,17 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { ManagementSetup } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { CloudSetup } from '../../cloud/public'; +import { SharePluginSetup } from '../../../../src/plugins/share/public'; import { BreadcrumbService } from './application/services/breadcrumbs'; -export interface PluginsDependencies { +export interface SetupDependencies { usageCollection?: UsageCollectionSetup; management: ManagementSetup; cloud?: CloudSetup; indexManagement?: IndexManagementPluginSetup; home?: HomePublicPluginSetup; + share: SharePluginSetup; } export interface ClientConfigType { diff --git a/x-pack/plugins/index_lifecycle_management/public/url_generator.ts b/x-pack/plugins/index_lifecycle_management/public/url_generator.ts new file mode 100644 index 00000000000000..a884c9a54a4b81 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/url_generator.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 { CoreSetup } from 'kibana/public'; +import { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public/'; +import { + getPoliciesListPath, + getPolicyCreatePath, + getPolicyEditPath, +} from './application/services/navigation'; +import { MANAGEMENT_APP_ID } from '../../../../src/plugins/management/public'; +import { SetupDependencies } from './types'; +import { PLUGIN } from '../common/constants'; + +export const ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID'; + +export interface IlmUrlGeneratorState { + page: 'policies_list' | 'policy_edit' | 'policy_create'; + policyName?: string; + absolute?: boolean; +} +export const createIlmUrlGenerator = ( + getAppBasePath: (absolute?: boolean) => Promise +): UrlGeneratorsDefinition => { + return { + id: ILM_URL_GENERATOR_ID, + createUrl: async (state: IlmUrlGeneratorState): Promise => { + switch (state.page) { + case 'policy_create': { + return `${await getAppBasePath(!!state.absolute)}${getPolicyCreatePath()}`; + } + case 'policy_edit': { + return `${await getAppBasePath(!!state.absolute)}${getPolicyEditPath(state.policyName!)}`; + } + case 'policies_list': { + return `${await getAppBasePath(!!state.absolute)}${getPoliciesListPath()}`; + } + } + }, + }; +}; + +export const registerUrlGenerator = ( + coreSetup: CoreSetup, + management: SetupDependencies['management'], + share: SetupDependencies['share'] +) => { + const getAppBasePath = async (absolute = false) => { + const [coreStart] = await coreSetup.getStartServices(); + return coreStart.application.getUrlForApp(MANAGEMENT_APP_ID, { + path: management.sections.section.data.getApp(PLUGIN.ID)!.basePath, + absolute, + }); + }; + + share.urlGenerators.registerUrlGenerator(createIlmUrlGenerator(getAppBasePath)); +}; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 8a610a04f8bb17..b5386dec342050 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -13,13 +13,13 @@ export type TestSubjects = | 'createTemplateButton' | 'dataStreamsEmptyPromptTemplateLink' | 'dataStreamTable' - | 'dataStreamTable' | 'deleteSystemTemplateCallOut' | 'deleteTemplateButton' | 'deleteTemplatesConfirmation' | 'documentationLink' | 'emptyPrompt' | 'filterList.filterItem' + | 'ilmPolicyLink' | 'includeStatsSwitch' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 82bd858240e1e1..6bf6c11a37bb41 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -7,6 +7,7 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; +import { EuiDescriptionListDescription } from '@elastic/eui'; import { registerTestBed, TestBed, @@ -26,15 +27,17 @@ export interface DataStreamsTabTestBed extends TestBed { clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; - clickDeletActionAt: (index: number) => void; + clickDeleteActionAt: (index: number) => void; clickConfirmDelete: () => void; - clickDeletDataStreamButton: () => void; + clickDeleteDataStreamButton: () => void; }; findDeleteActionAt: (index: number) => ReactWrapper; findDeleteConfirmationModal: () => ReactWrapper; findDetailPanel: () => ReactWrapper; findDetailPanelTitle: () => string; findEmptyPromptIndexTemplateLink: () => ReactWrapper; + findDetailPanelIlmPolicyLink: () => ReactWrapper; + findDetailPanelIlmPolicyName: () => ReactWrapper; } export const setup = async (overridingDependencies: any = {}): Promise => { @@ -115,7 +118,7 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const clickDeleteActionAt = (index: number) => { findDeleteActionAt(index).simulate('click'); }; @@ -135,7 +138,7 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const clickDeleteDataStreamButton = () => { const { find } = testBed; find('deleteDataStreamButton').simulate('click'); }; @@ -150,6 +153,17 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find } = testBed; + return find('ilmPolicyLink'); + }; + + const findDetailPanelIlmPolicyName = () => { + const descriptionList = testBed.component.find(EuiDescriptionListDescription); + // ilm policy is the last in the details list + return descriptionList.last(); + }; + return { ...testBed, actions: { @@ -159,15 +173,17 @@ export const setup = async (overridingDependencies: any = {}): Promise { ]); setLoadDataStreamResponse(dataStreamForDetailPanel); - testBed = await setup(); + testBed = await setup({ history: createMemoryHistory() }); await act(async () => { testBed.actions.goToDataStreamsList(); }); @@ -176,19 +177,19 @@ describe('Data Streams tab', () => { describe('deleting a data stream', () => { test('shows a confirmation modal', async () => { const { - actions: { clickDeletActionAt }, + actions: { clickDeleteActionAt }, findDeleteConfirmationModal, } = testBed; - clickDeletActionAt(0); + clickDeleteActionAt(0); const confirmationModal = findDeleteConfirmationModal(); expect(confirmationModal).toBeDefined(); }); test('sends a request to the Delete API', async () => { const { - actions: { clickDeletActionAt, clickConfirmDelete }, + actions: { clickDeleteActionAt, clickConfirmDelete }, } = testBed; - clickDeletActionAt(0); + clickDeleteActionAt(0); httpRequestsMockHelpers.setDeleteDataStreamResponse({ results: { @@ -219,12 +220,12 @@ describe('Data Streams tab', () => { test('deletes the data stream when delete button is clicked', async () => { const { - actions: { clickNameAt, clickDeletDataStreamButton, clickConfirmDelete }, + actions: { clickNameAt, clickDeleteDataStreamButton, clickConfirmDelete }, } = testBed; await clickNameAt(0); - clickDeletDataStreamButton(); + clickDeleteDataStreamButton(); httpRequestsMockHelpers.setDeleteDataStreamResponse({ results: { @@ -263,7 +264,9 @@ describe('Data Streams tab', () => { setLoadDataStreamsResponse([dataStreamDollarSign]); setLoadDataStreamResponse(dataStreamDollarSign); - testBed = await setup(); + testBed = await setup({ + history: createMemoryHistory(), + }); await act(async () => { testBed.actions.goToDataStreamsList(); }); @@ -287,4 +290,82 @@ describe('Data Streams tab', () => { }); }); }); + + describe('url generators', () => { + const mockIlmUrlGenerator = { + getUrlGenerator: () => ({ + createUrl: ({ policyName }: { policyName: string }) => `/test/${policyName}`, + }), + }; + test('with an ILM url generator and an ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = { + ...createDataStreamPayload('dataStream1'), + ilmPolicyName: 'my_ilm_policy', + }; + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: mockIlmUrlGenerator, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().prop('href')).toBe('/test/my_ilm_policy'); + }); + + test('with an ILM url generator and no ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: mockIlmUrlGenerator, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); + expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy(); + }); + + test('without an ILM url generator and with an ILM policy', async () => { + const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; + + const dataStreamForDetailPanel = { + ...createDataStreamPayload('dataStream1'), + ilmPolicyName: 'my_ilm_policy', + }; + setLoadDataStreamsResponse([dataStreamForDetailPanel]); + setLoadDataStreamResponse(dataStreamForDetailPanel); + + testBed = await setup({ + history: createMemoryHistory(), + urlGenerators: { getUrlGenerator: () => {} }, + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); + expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy(); + }); + }); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index b660adb9eec08a..c7af3a8f451050 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -26,8 +26,6 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); - export interface IndicesTestBed extends TestBed { actions: { selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; @@ -39,7 +37,11 @@ export interface IndicesTestBed extends TestBed { findDataStreamDetailPanelTitle: () => string; } -export const setup = async (): Promise => { +export const setup = async (overridingDependencies: any = {}): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(IndexManagementHome, overridingDependencies), + testBedConfig + ); const testBed = await initTestBed(); /** diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 3d6d94d1658559..adc47515acaee5 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -18,6 +18,7 @@ import { createDataStreamPayload } from './data_streams_tab.helpers'; at createWorker (//node_modules/brace/index.js:17992:5) */ import { stubWebWorker } from '../../../../../test_utils/stub_web_worker'; +import { createMemoryHistory } from 'history'; stubWebWorker(); describe('', () => { @@ -75,7 +76,9 @@ describe('', () => { httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1')); - testBed = await setup(); + testBed = await setup({ + history: createMemoryHistory(), + }); await act(async () => { const { component } = testBed; diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json index 28846414ca2e88..ff6a71d53894a3 100644 --- a/x-pack/plugins/index_management/kibana.json +++ b/x-pack/plugins/index_management/kibana.json @@ -7,7 +7,8 @@ "home", "licensing", "management", - "features" + "features", + "share" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 22e6f09907d75d..c654288aaceb81 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -14,6 +14,7 @@ import { IngestManagerSetup } from '../../../ingest_manager/public'; import { IndexMgmtMetricsType } from '../types'; import { UiMetricService, NotificationService, HttpService } from './services'; import { ExtensionsService } from '../services'; +import { SharePluginStart } from '../../../../../src/plugins/share/public'; const AppContext = createContext(undefined); @@ -35,6 +36,7 @@ export interface AppDependencies { history: ScopedHistory; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; uiSettings: CoreSetup['uiSettings']; + urlGenerators: SharePluginStart['urlGenerators']; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.ts new file mode 100644 index 00000000000000..1b71e389ee3544 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/constants/ilm_url_generator.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 ILM_URL_GENERATOR_ID = 'ILM_URL_GENERATOR_ID'; +export const ILM_PAGES_POLICY_EDIT = 'policy_edit'; diff --git a/x-pack/plugins/index_management/public/application/constants/index.ts b/x-pack/plugins/index_management/public/application/constants/index.ts index d55d28dce70032..80c8779f2f020f 100644 --- a/x-pack/plugins/index_management/public/application/constants/index.ts +++ b/x-pack/plugins/index_management/public/application/constants/index.ts @@ -15,3 +15,5 @@ export { } from './detail_panel_tabs'; export const REACT_ROOT_ID = 'indexManagementReactRoot'; + +export * from './ilm_url_generator'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index f7b728c8757627..52528d3c511454 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -12,7 +12,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { IngestManagerSetup } from '../../../ingest_manager/public'; import { PLUGIN } from '../../common/constants'; import { ExtensionsService } from '../services'; -import { IndexMgmtMetricsType } from '../types'; +import { IndexMgmtMetricsType, StartDependencies } from '../types'; import { AppDependencies } from './app_context'; import { breadcrumbService } from './services/breadcrumbs'; import { documentationService } from './services/documentation'; @@ -28,14 +28,14 @@ interface InternalServices { } export async function mountManagementSection( - coreSetup: CoreSetup, + coreSetup: CoreSetup, usageCollection: UsageCollectionSetup, services: InternalServices, params: ManagementAppMountParams, ingestManager?: IngestManagerSetup ) { const { element, setBreadcrumbs, history } = params; - const [core] = await coreSetup.getStartServices(); + const [core, startDependencies] = await coreSetup.getStartServices(); const { docLinks, fatalErrors, @@ -44,6 +44,7 @@ export async function mountManagementSection( uiSettings, } = core; + const { urlGenerators } = startDependencies.share; docTitle.change(PLUGIN.getI18nName(i18n)); breadcrumbService.setup(setBreadcrumbs); @@ -62,6 +63,7 @@ export async function mountManagementSection( history, setBreadcrumbs, uiSettings, + urlGenerators, }; const unmountAppCallback = renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 0af22b497a4c0e..9ec6993717435a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -28,6 +28,10 @@ import { SectionLoading, SectionError, Error, DataHealth } from '../../../../com import { useLoadDataStream } from '../../../../services/api'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; import { humanizeTimeStamp } from '../humanize_time_stamp'; +import { useUrlGenerator } from '../../../../services/use_url_generator'; +import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../constants'; +import { useAppContext } from '../../../../app_context'; +import { getIndexListUri } from '../../../../..'; interface DetailsListProps { details: Array<{ @@ -72,19 +76,26 @@ const DetailsList: React.FunctionComponent = ({ details }) => interface Props { dataStreamName: string; - backingIndicesLink: ReturnType; onClose: (shouldReload?: boolean) => void; } export const DataStreamDetailPanel: React.FunctionComponent = ({ dataStreamName, - backingIndicesLink, onClose, }) => { const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName); const [isDeleting, setIsDeleting] = useState(false); + const ilmPolicyLink = useUrlGenerator({ + urlGeneratorId: ILM_URL_GENERATOR_ID, + urlGeneratorState: { + page: ILM_PAGES_POLICY_EDIT, + policyName: dataStream?.ilmPolicyName, + }, + }); + const { history } = useAppContext(); + let content; if (isLoading) { @@ -159,7 +170,16 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { defaultMessage: `The data stream's current backing indices`, }), - content: {indices.length}, + content: ( + + {indices.length} + + ), }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', { @@ -196,13 +216,23 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { defaultMessage: `The index lifecycle policy that manages the data stream's data`, }), - content: ilmPolicyName ?? ( - - {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { - defaultMessage: `None`, - })} - - ), + content: + ilmPolicyName && ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName || ( + + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { + defaultMessage: `None`, + })} + + ) + ), }, ]; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 19286523055f5c..ba79319b566bfa 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -28,7 +28,6 @@ import { import { useAppContext } from '../../../app_context'; import { SectionError, SectionLoading, Error } from '../../../components'; import { useLoadDataStreams } from '../../../services/api'; -import { getIndexListUri } from '../../../services/routing'; import { documentationService } from '../../../services/documentation'; import { Section } from '../home'; import { DataStreamTable } from './data_stream_table'; @@ -233,10 +232,6 @@ export const DataStreamList: React.FunctionComponent { history.push(`/${Section.DataStreams}`); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index 0c403e69d2e765..961a171f9f05ad 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -19,9 +19,9 @@ import { EuiCodeBlock, EuiSpacer, } from '@elastic/eui'; -import { useAppContext } from '../../../../../app_context'; import { TemplateDeserialized } from '../../../../../../../common'; -import { getILMPolicyPath } from '../../../../../services/routing'; +import { ILM_PAGES_POLICY_EDIT, ILM_URL_GENERATOR_ID } from '../../../../../constants'; +import { useUrlGenerator } from '../../../../../services/use_url_generator'; interface Props { templateDetails: TemplateDeserialized; @@ -53,9 +53,13 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) const numIndexPatterns = indexPatterns.length; - const { - core: { getUrlForApp }, - } = useAppContext(); + const ilmPolicyLink = useUrlGenerator({ + urlGeneratorId: ILM_URL_GENERATOR_ID, + urlGeneratorState: { + page: ILM_PAGES_POLICY_EDIT, + policyName: ilmPolicy?.name, + }, + }); return ( <> @@ -159,16 +163,10 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) /> - {ilmPolicy && ilmPolicy.name ? ( - - {ilmPolicy.name} - + {ilmPolicy?.name && ilmPolicyLink ? ( + {ilmPolicy!.name} ) : ( - i18nTexts.none + ilmPolicy?.name || i18nTexts.none )} diff --git a/x-pack/plugins/index_management/public/application/services/use_url_generator.ts b/x-pack/plugins/index_management/public/application/services/use_url_generator.ts new file mode 100644 index 00000000000000..4e6bd05aa14b5c --- /dev/null +++ b/x-pack/plugins/index_management/public/application/services/use_url_generator.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 { useEffect, useState } from 'react'; +import { + UrlGeneratorContract, + UrlGeneratorId, + UrlGeneratorStateMapping, +} from '../../../../../../src/plugins/share/public'; +import { useAppContext } from '../app_context'; + +export const useUrlGenerator = ({ + urlGeneratorId, + urlGeneratorState, +}: { + urlGeneratorId: UrlGeneratorId; + urlGeneratorState: UrlGeneratorStateMapping[UrlGeneratorId]['State']; +}) => { + const { urlGenerators } = useAppContext(); + const [link, setLink] = useState(); + useEffect(() => { + const updateLink = async (): Promise => { + let urlGenerator: UrlGeneratorContract; + try { + urlGenerator = urlGenerators.getUrlGenerator(urlGeneratorId); + const url = await urlGenerator.createUrl(urlGeneratorState); + setLink(url); + } catch (e) { + // do nothing + } + }; + + updateLink(); + }, [urlGeneratorId, urlGeneratorState, urlGenerators]); + return link; +}; diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index 538dcaf25c47d1..da6d90f22a3840 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import './index.scss'; -import { IndexMgmtUIPlugin, IndexManagementPluginSetup } from './plugin'; +import { IndexMgmtUIPlugin } from './plugin'; /** @public */ export const plugin = () => { return new IndexMgmtUIPlugin(); }; -export { IndexManagementPluginSetup }; +export { IndexManagementPluginSetup } from './types'; export { getIndexListUri } from './application/services/routing'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 6139ed5d2e6ad0..855486528b7974 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -6,10 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '../../../../src/core/public'; -import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; -import { IngestManagerSetup } from '../../ingest_manager/public'; import { UIM_APP_NAME, PLUGIN } from '../common/constants'; import { httpService } from './application/services/http'; @@ -19,18 +16,13 @@ import { UiMetricService } from './application/services/ui_metric'; import { setExtensionsService } from './application/store/selectors'; import { setUiMetricService } from './application/services/api'; -import { IndexMgmtMetricsType } from './types'; -import { ExtensionsService, ExtensionsSetup } from './services'; - -export interface IndexManagementPluginSetup { - extensionsService: ExtensionsSetup; -} - -interface PluginsDependencies { - ingestManager?: IngestManagerSetup; - usageCollection: UsageCollectionSetup; - management: ManagementSetup; -} +import { + IndexManagementPluginSetup, + IndexMgmtMetricsType, + SetupDependencies, + StartDependencies, +} from './types'; +import { ExtensionsService } from './services'; export class IndexMgmtUIPlugin { private uiMetricService = new UiMetricService(UIM_APP_NAME); @@ -43,7 +35,10 @@ export class IndexMgmtUIPlugin { setUiMetricService(this.uiMetricService); } - public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexManagementPluginSetup { + public setup( + coreSetup: CoreSetup, + plugins: SetupDependencies + ): IndexManagementPluginSetup { const { http, notifications } = coreSetup; const { ingestManager, usageCollection, management } = plugins; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 08fbf634502187..f860b89b0ba0c7 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -4,4 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ExtensionsSetup } from './services'; +import { IngestManagerSetup } from '../../ingest_manager/public'; +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { SharePluginStart } from '../../../../src/plugins/share/public'; + export type IndexMgmtMetricsType = 'loaded' | 'click' | 'count'; + +export interface IndexManagementPluginSetup { + extensionsService: ExtensionsSetup; +} + +export interface SetupDependencies { + ingestManager?: IngestManagerSetup; + usageCollection: UsageCollectionSetup; + management: ManagementSetup; +} + +export interface StartDependencies { + share: SharePluginStart; +} diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index 4c8c610794b2eb..214bb16b242839 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { useEffect, useState, useReducer, useCallback } from 'react'; +import { useMountedState } from 'react-use'; import createContainer from 'constate'; import { pick, throttle } from 'lodash'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; @@ -146,15 +147,20 @@ const useFetchEntriesEffect = ( props: LogEntriesProps ) => { const { services } = useKibanaContextForPlugin(); + const isMounted = useMountedState(); const [prevParams, cachePrevParams] = useState(); const [startedStreaming, setStartedStreaming] = useState(false); + const dispatchIfMounted = useCallback((action) => (isMounted() ? dispatch(action) : undefined), [ + dispatch, + isMounted, + ]); const runFetchNewEntriesRequest = async (overrides: Partial = {}) => { if (!props.startTimestamp || !props.endTimestamp) { return; } - dispatch({ type: Action.FetchingNewEntries }); + dispatchIfMounted({ type: Action.FetchingNewEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -175,13 +181,15 @@ const useFetchEntriesEffect = ( }; const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ type: Action.ReceiveNewEntries, payload }); + dispatchIfMounted({ type: Action.ReceiveNewEntries, payload }); // Move position to the bottom if it's the first load. // Do it in the next tick to allow the `dispatch` to fire if (!props.timeKey && payload.bottomCursor) { setTimeout(() => { - props.jumpToTargetPosition(payload.bottomCursor!); + if (isMounted()) { + props.jumpToTargetPosition(payload.bottomCursor!); + } }); } else if ( props.timeKey && @@ -192,7 +200,7 @@ const useFetchEntriesEffect = ( props.jumpToTargetPosition(payload.topCursor); } } catch (e) { - dispatch({ type: Action.ErrorOnNewEntries }); + dispatchIfMounted({ type: Action.ErrorOnNewEntries }); } }; @@ -210,7 +218,7 @@ const useFetchEntriesEffect = ( return; } - dispatch({ type: Action.FetchingMoreEntries }); + dispatchIfMounted({ type: Action.FetchingMoreEntries }); try { const commonFetchArgs: LogEntriesBaseRequest = { @@ -232,14 +240,14 @@ const useFetchEntriesEffect = ( const { data: payload } = await fetchLogEntries(fetchArgs, services.http.fetch); - dispatch({ + dispatchIfMounted({ type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, payload, }); return payload.bottomCursor; } catch (e) { - dispatch({ type: Action.ErrorOnMoreEntries }); + dispatchIfMounted({ type: Action.ErrorOnMoreEntries }); } }; @@ -322,7 +330,7 @@ const useFetchEntriesEffect = ( after: props.endTimestamp > prevParams.endTimestamp, }; - dispatch({ type: Action.ExpandRange, payload: shouldExpand }); + dispatchIfMounted({ type: Action.ExpandRange, payload: shouldExpand }); }; const expandRangeEffectDependencies = [ diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts index 9951b62fa64a30..42518127f68bf7 100644 --- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts @@ -6,13 +6,15 @@ /* eslint-disable max-classes-per-file */ -import { DependencyList, useEffect, useMemo, useRef, useState } from 'react'; +import { DependencyList, useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import { useMountedState } from 'react-use'; interface UseTrackedPromiseArgs { createPromise: (...args: Arguments) => Promise; onResolve?: (result: Result) => void; onReject?: (value: unknown) => void; cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never'; + triggerOrThrow?: 'always' | 'whenMounted'; } /** @@ -64,6 +66,16 @@ interface UseTrackedPromiseArgs { * The last argument is a normal React hook dependency list that indicates * under which conditions a new reference to the configuration object should be * used. + * + * The `onResolve`, `onReject` and possible uncatched errors are only triggered + * if the underlying component is mounted. To ensure they always trigger (i.e. + * if the promise is called in a `useLayoutEffect`) use the `triggerOrThrow` + * attribute: + * + * 'whenMounted': (default) they are called only if the component is mounted. + * + * 'always': they always call. The consumer is then responsible of ensuring no + * side effects happen if the underlying component is not mounted. */ export const useTrackedPromise = ( { @@ -71,9 +83,20 @@ export const useTrackedPromise = ( onResolve = noOp, onReject = noOp, cancelPreviousOn = 'never', + triggerOrThrow = 'whenMounted', }: UseTrackedPromiseArgs, dependencies: DependencyList ) => { + const isComponentMounted = useMountedState(); + const shouldTriggerOrThrow = useCallback(() => { + switch (triggerOrThrow) { + case 'always': + return true; + case 'whenMounted': + return isComponentMounted(); + } + }, [isComponentMounted, triggerOrThrow]); + /** * If a promise is currently pending, this holds a reference to it and its * cancellation function. @@ -144,7 +167,7 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onResolve) { + if (onResolve && shouldTriggerOrThrow()) { onResolve(value); } @@ -173,11 +196,13 @@ export const useTrackedPromise = ( (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise ); - if (onReject) { - onReject(value); - } + if (shouldTriggerOrThrow()) { + if (onReject) { + onReject(value); + } - throw value; + throw value; + } } ), }; diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts index 91396bce359b04..e81207300a5f31 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.test.ts @@ -84,16 +84,14 @@ describe('Ingest Manager - packageToPackagePolicy', () => { { type: 'foo', enabled: true, - streams: [ - { id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }, - ], + streams: [{ enabled: true, data_stream: { dataset: 'foo', type: 'logs' } }], }, { type: 'bar', enabled: true, streams: [ - { id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' } }, - { id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } }, + { enabled: true, data_stream: { dataset: 'bar', type: 'logs' } }, + { enabled: true, data_stream: { dataset: 'bar2', type: 'logs' } }, ], }, ]); @@ -142,7 +140,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: true, streams: [ { - id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' }, vars: { 'var-name': { value: 'foo-var-value' } }, @@ -154,13 +151,11 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: true, streams: [ { - id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' }, vars: { 'var-name': { type: 'text', value: 'bar-var-value' } }, }, { - id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' }, vars: { 'var-name': { type: 'yaml', value: 'bar2-var-value' } }, @@ -258,7 +253,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, streams: [ { - id: 'foo-foo', enabled: true, data_stream: { dataset: 'foo', type: 'logs' }, vars: { @@ -276,7 +270,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, streams: [ { - id: 'bar-bar', enabled: true, data_stream: { dataset: 'bar', type: 'logs' }, vars: { @@ -284,7 +277,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, }, { - id: 'bar-bar2', enabled: true, data_stream: { dataset: 'bar2', type: 'logs' }, vars: { @@ -298,7 +290,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { enabled: false, streams: [ { - id: 'with-disabled-streams-disabled', enabled: false, data_stream: { dataset: 'disabled', type: 'logs' }, vars: { @@ -306,7 +297,6 @@ describe('Ingest Manager - packageToPackagePolicy', () => { }, }, { - id: 'with-disabled-streams-disabled2', enabled: false, data_stream: { dataset: 'disabled2', type: 'logs' }, }, diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts index 822747916ebc5c..cbdfa25ed7f7e1 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_package_policy.ts @@ -8,11 +8,10 @@ import { RegistryPolicyTemplate, RegistryVarsEntry, RegistryStream, - PackagePolicy, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, - PackagePolicyInput, - PackagePolicyInputStream, + NewPackagePolicyInput, + NewPackagePolicyInputStream, NewPackagePolicy, } from '../types'; @@ -42,8 +41,10 @@ const getStreamsForInputType = ( /* * This service creates a package policy inputs definition from defaults provided in package info */ -export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackagePolicy['inputs'] => { - const inputs: PackagePolicy['inputs'] = []; +export const packageToPackagePolicyInputs = ( + packageInfo: PackageInfo +): NewPackagePolicy['inputs'] => { + const inputs: NewPackagePolicy['inputs'] = []; // Assume package will only ever ship one package policy template for now const packagePolicyTemplate: RegistryPolicyTemplate | null = @@ -71,12 +72,11 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP }; // Map each package input stream into package policy input stream - const streams: PackagePolicyInputStream[] = getStreamsForInputType( + const streams: NewPackagePolicyInputStream[] = getStreamsForInputType( packageInput.type, packageInfo ).map((packageStream) => { - const stream: PackagePolicyInputStream = { - id: `${packageInput.type}-${packageStream.data_stream.dataset}`, + const stream: NewPackagePolicyInputStream = { enabled: packageStream.enabled === false ? false : true, data_stream: packageStream.data_stream, }; @@ -86,7 +86,7 @@ export const packageToPackagePolicyInputs = (packageInfo: PackageInfo): PackageP return stream; }); - const input: PackagePolicyInput = { + const input: NewPackagePolicyInput = { type: packageInput.type, enabled: streams.length ? !!streams.find((stream) => stream.enabled) : true, streams, diff --git a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts index 724dbae5dac853..ae16899a4b6f99 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/package_policy.ts @@ -18,7 +18,6 @@ export interface PackagePolicyConfigRecordEntry { export type PackagePolicyConfigRecord = Record; export interface NewPackagePolicyInputStream { - id: string; enabled: boolean; data_stream: { dataset: string; @@ -29,6 +28,7 @@ export interface NewPackagePolicyInputStream { } export interface PackagePolicyInputStream extends NewPackagePolicyInputStream { + id: string; compiled_stream?: any; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx index 175bfb14699020..177354dad14dcc 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx @@ -14,7 +14,7 @@ import { EuiSpacer, EuiButtonEmpty, } from '@elastic/eui'; -import { PackagePolicyInput, RegistryVarsEntry } from '../../../../types'; +import { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../types'; import { isAdvancedVar, PackagePolicyConfigValidationResults, @@ -28,8 +28,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)` export const PackagePolicyInputConfig: React.FunctionComponent<{ packageInputVars?: RegistryVarsEntry[]; - packagePolicyInput: PackagePolicyInput; - updatePackagePolicyInput: (updatedInput: Partial) => void; + packagePolicyInput: NewPackagePolicyInput; + updatePackagePolicyInput: (updatedInput: Partial) => void; inputVarsValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; }> = memo( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx index 1e43cc0d5938ee..79ff0cc29850c8 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_panel.tsx @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { - PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, RegistryInput, RegistryStream, @@ -40,7 +40,7 @@ const ShortenedHorizontalRule = styled(EuiHorizontalRule)` const shouldShowStreamsByDefault = ( packageInput: RegistryInput, packageInputStreams: Array, - packagePolicyInput: PackagePolicyInput + packagePolicyInput: NewPackagePolicyInput ): boolean => { return ( packagePolicyInput.enabled && @@ -63,8 +63,8 @@ const shouldShowStreamsByDefault = ( export const PackagePolicyInputPanel: React.FunctionComponent<{ packageInput: RegistryInput; packageInputStreams: Array; - packagePolicyInput: PackagePolicyInput; - updatePackagePolicyInput: (updatedInput: Partial) => void; + packagePolicyInput: NewPackagePolicyInput; + updatePackagePolicyInput: (updatedInput: Partial) => void; inputValidationResults: PackagePolicyInputValidationResults; forceShowErrors?: boolean; }> = memo( @@ -210,7 +210,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ ...updatedStream, }; - const updatedInput: Partial = { + const updatedInput: Partial = { streams: newStreams, }; @@ -227,7 +227,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ updatePackagePolicyInput(updatedInput); }} inputStreamValidationResults={ - inputValidationResults.streams![packagePolicyInputStream!.id] + inputValidationResults.streams![packagePolicyInputStream!.data_stream!.dataset] } forceShowErrors={forceShowErrors} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx index 3d33edd468151d..963d0da50ce7f9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx @@ -16,7 +16,7 @@ import { EuiSpacer, EuiButtonEmpty, } from '@elastic/eui'; -import { PackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; +import { NewPackagePolicyInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; import { isAdvancedVar, PackagePolicyConfigValidationResults, @@ -30,8 +30,8 @@ const FlexItemWithMaxWidth = styled(EuiFlexItem)` export const PackagePolicyInputStreamConfig: React.FunctionComponent<{ packageInputStream: RegistryStream; - packagePolicyInputStream: PackagePolicyInputStream; - updatePackagePolicyInputStream: (updatedStream: Partial) => void; + packagePolicyInputStream: NewPackagePolicyInputStream; + updatePackagePolicyInputStream: (updatedStream: Partial) => void; inputStreamValidationResults: PackagePolicyConfigValidationResults; forceShowErrors?: boolean; }> = memo( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts similarity index 91% rename from x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts rename to x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts index 9022e312ece79c..8d46fed1ff14e2 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test..ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts @@ -154,7 +154,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'foo-foo', data_stream: { dataset: 'foo', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, @@ -170,13 +169,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'bar-bar', data_stream: { dataset: 'bar', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, }, { - id: 'bar-bar2', data_stream: { dataset: 'bar2', type: 'logs' }, enabled: true, vars: { 'var-name': { value: undefined, type: 'text' } }, @@ -193,13 +190,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { enabled: true, streams: [ { - id: 'with-disabled-streams-disabled', data_stream: { dataset: 'disabled', type: 'logs' }, enabled: false, vars: { 'var-name': { value: undefined, type: 'text' } }, }, { - id: 'with-disabled-streams-disabled-without-vars', data_stream: { dataset: 'disabled2', type: 'logs' }, enabled: false, }, @@ -213,8 +208,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'with-no-stream-vars-bar', - data_stream: { dataset: 'bar', type: 'logs' }, + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, enabled: true, }, ], @@ -236,7 +230,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'foo-foo', data_stream: { dataset: 'foo', type: 'logs' }, enabled: true, vars: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, @@ -252,13 +245,11 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'bar-bar', data_stream: { dataset: 'bar', type: 'logs' }, enabled: true, vars: { 'var-name': { value: ' \n\n', type: 'yaml' } }, }, { - id: 'bar-bar2', data_stream: { dataset: 'bar2', type: 'logs' }, enabled: true, vars: { 'var-name': { value: undefined, type: 'text' } }, @@ -275,7 +266,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { enabled: true, streams: [ { - id: 'with-disabled-streams-disabled', data_stream: { dataset: 'disabled', type: 'logs' }, enabled: false, vars: { @@ -286,7 +276,6 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, }, { - id: 'with-disabled-streams-disabled-without-vars', data_stream: { dataset: 'disabled2', type: 'logs' }, enabled: false, }, @@ -300,8 +289,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }, streams: [ { - id: 'with-no-stream-vars-bar', - data_stream: { dataset: 'bar', type: 'logs' }, + data_stream: { dataset: 'with-no-stream-vars-bar', type: 'logs' }, enabled: true, }, ], @@ -320,21 +308,21 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': null, 'foo-input3-var-name': null, }, - streams: { 'foo-foo': { vars: { 'var-name': null } } }, + streams: { foo: { vars: { 'var-name': null } } }, }, bar: { vars: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, streams: { - 'bar-bar': { vars: { 'var-name': null } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { + disabled: { vars: { 'var-name': null }, }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled2: {}, }, }, 'with-no-stream-vars': { @@ -364,7 +352,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': ['foo-input2-var-name is required'], 'foo-input3-var-name': ['foo-input3-var-name is required'], }, - streams: { 'foo-foo': { vars: { 'var-name': ['Invalid YAML format'] } } }, + streams: { foo: { vars: { 'var-name': ['Invalid YAML format'] } } }, }, bar: { vars: { @@ -372,14 +360,14 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'bar-input2-var-name': ['bar-input2-var-name is required'], }, streams: { - 'bar-bar': { vars: { 'var-name': ['var-name is required'] } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': ['var-name is required'] } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { vars: { 'var-name': null } }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled: { vars: { 'var-name': null } }, + disabled2: {}, }, }, 'with-no-stream-vars': { @@ -427,7 +415,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'foo-input2-var-name': ['foo-input2-var-name is required'], 'foo-input3-var-name': ['foo-input3-var-name is required'], }, - streams: { 'foo-foo': { vars: { 'var-name': null } } }, + streams: { foo: { vars: { 'var-name': null } } }, }, bar: { vars: { @@ -435,16 +423,16 @@ describe('Ingest Manager - validatePackagePolicy()', () => { 'bar-input2-var-name': ['bar-input2-var-name is required'], }, streams: { - 'bar-bar': { vars: { 'var-name': null } }, - 'bar-bar2': { vars: { 'var-name': null } }, + bar: { vars: { 'var-name': null } }, + bar2: { vars: { 'var-name': null } }, }, }, 'with-disabled-streams': { streams: { - 'with-disabled-streams-disabled': { + disabled: { vars: { 'var-name': null }, }, - 'with-disabled-streams-disabled-without-vars': {}, + disabled2: {}, }, }, 'with-no-stream-vars': { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts index 9ce73c0690ccbe..1126cd7e58e186 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts @@ -151,7 +151,7 @@ export const validatePackagePolicy = ( ); } - inputValidationResults.streams![stream.id] = streamValidationResults; + inputValidationResults.streams![stream.data_stream.dataset] = streamValidationResults; }); } else { delete inputValidationResults.streams; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index d3d5e60c34e584..b335ff439684bb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -5,7 +5,12 @@ */ import React from 'react'; import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { PackageInfo, RegistryStream, NewPackagePolicy, PackagePolicyInput } from '../../../types'; +import { + PackageInfo, + RegistryStream, + NewPackagePolicy, + NewPackagePolicyInput, +} from '../../../types'; import { Loading } from '../../../components'; import { PackagePolicyValidationResults } from './services'; import { PackagePolicyInputPanel, CustomPackagePolicy } from './components'; @@ -71,7 +76,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ packageInput={packageInput} packageInputStreams={packageInputStreams} packagePolicyInput={packagePolicyInput} - updatePackagePolicyInput={(updatedInput: Partial) => { + updatePackagePolicyInput={(updatedInput: Partial) => { const indexOfUpdatedInput = packagePolicy.inputs.findIndex( (input) => input.type === packageInput.type ); 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 386ffa5649cc23..1cf8077aeda404 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 @@ -17,7 +17,9 @@ export { NewPackagePolicy, UpdatePackagePolicy, PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, + NewPackagePolicyInputStream, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, Output, diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index 198a54ca84125b..1d221b8b1eeadd 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -35,8 +35,7 @@ import { getPackageInfo, handleInstallPackageFailure, isBulkInstallError, - installPackageFromRegistry, - installPackageByUpload, + installPackage, removeInstallation, getLimitedPackages, getInstallationObject, @@ -149,7 +148,8 @@ export const installPackageFromRegistryHandler: RequestHandler< const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); try { - const res = await installPackageFromRegistry({ + const res = await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey, callCluster, @@ -224,7 +224,8 @@ export const installPackageByUploadHandler: RequestHandler< const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); try { - const res = await installPackageByUpload({ + const res = await installPackage({ + installSource: 'upload', savedObjectsClient, callCluster, archiveBuffer, diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts index 44c2ccda3bd2a5..f47b8499a1b69e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.test.ts @@ -28,6 +28,13 @@ jest.mock('../../services/package_policy', (): { create: jest.fn((soClient, callCluster, newData) => Promise.resolve({ ...newData, + inputs: newData.inputs.map((input) => ({ + ...input, + streams: input.streams.map((stream) => ({ + id: stream.data_stream.dataset, + ...stream, + })), + })), id: '1', revision: 1, updated_at: new Date().toISOString(), diff --git a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts index d9baeca4deb471..3a2b9ba7a744f7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/package_policy/handlers.ts @@ -7,7 +7,6 @@ import { TypeOf } from '@kbn/config-schema'; import Boom from '@hapi/boom'; import { RequestHandler, SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; import { appContextService, packagePolicyService } from '../../services'; -import { getPackageInfo } from '../../services/epm/packages'; import { GetPackagePoliciesRequestSchema, GetOnePackagePolicyRequestSchema, @@ -134,21 +133,11 @@ export const updatePackagePolicyHandler: RequestHandler< const newData = { ...request.body }; const pkg = newData.package || packagePolicy.package; const inputs = newData.inputs || packagePolicy.inputs; - if (pkg && (newData.inputs || newData.package)) { - const pkgInfo = await getPackageInfo({ - savedObjectsClient: soClient, - pkgName: pkg.name, - pkgVersion: pkg.version, - }); - newData.inputs = (await packagePolicyService.assignPackageStream(pkgInfo, inputs)) as TypeOf< - typeof CreatePackagePolicyRequestSchema.body - >['inputs']; - } const updatedPackagePolicy = await packagePolicyService.update( soClient, request.params.packagePolicyId, - newData, + { ...newData, package: pkg, inputs }, { user } ); return response.ok({ diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts similarity index 95% rename from x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts rename to x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts index 695db9db73fa25..102324c18bd430 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/cache.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { pkgToPkgKey } from './index'; +import { pkgToPkgKey } from '../registry/index'; const cache: Map = new Map(); export const cacheGet = (key: string) => cache.get(key); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts index 395f9c15b3b878..27451ed6b5e609 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts @@ -4,21 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import yaml from 'js-yaml'; -import { uniq } from 'lodash'; - -import { - ArchivePackage, - RegistryPolicyTemplate, - RegistryDataStream, - RegistryInput, - RegistryStream, - RegistryVarsEntry, -} from '../../../../common/types'; +import { ArchivePackage } from '../../../../common/types'; import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors'; -import { pkgToPkgKey } from '../registry'; -import { cacheGet, cacheSet, setArchiveFilelist } from '../registry/cache'; +import { + cacheSet, + cacheDelete, + getArchiveFilelist, + setArchiveFilelist, + deleteArchiveFilelist, +} from './cache'; import { ArchiveEntry, getBufferExtractor } from '../registry/extract'; +import { parseAndVerifyArchive } from './validation'; + +export * from './cache'; export async function loadArchivePackage({ archiveBuffer, @@ -53,7 +51,7 @@ export async function unpackArchiveToCache( await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { const { path, buffer } = entry; // skip directories - if (path.slice(-1) === '/') return; + if (path.endsWith('/')) return; if (buffer) { cacheSet(path, buffer); paths.push(path); @@ -61,7 +59,7 @@ export async function unpackArchiveToCache( }); } catch (error) { throw new PackageInvalidArchiveError( - `Error during extraction of uploaded package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.` + `Error during extraction of package: ${error}. Assumed content type was ${contentType}, check if this matches the archive type.` ); } @@ -69,257 +67,20 @@ export async function unpackArchiveToCache( // unpacking a zip file with untarBuffer() just results in nothing. if (paths.length === 0) { throw new PackageInvalidArchiveError( - `Uploaded archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.` + `Archive seems empty. Assumed content type was ${contentType}, check if this matches the archive type.` ); } return paths; } -// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the -// package registry. At some point this should probably be replaced (or enhanced) with verification based on -// https://github.com/elastic/package-spec/ - -function parseAndVerifyArchive(paths: string[]): ArchivePackage { - // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present - const toplevelDir = paths[0].split('/')[0]; - paths.forEach((path) => { - if (path.split('/')[0] !== toplevelDir) { - throw new PackageInvalidArchiveError('Package contains more than one top-level directory.'); - } - }); - - // The package must contain a manifest file ... - const manifestFile = `${toplevelDir}/manifest.yml`; - const manifestBuffer = cacheGet(manifestFile); - if (!paths.includes(manifestFile) || !manifestBuffer) { - throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.'); - } - - // ... which must be valid YAML - let manifest; - try { - manifest = yaml.load(manifestBuffer.toString()); - } catch (error) { - throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); - } - - // Package name and version from the manifest must match those from the toplevel directory - const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); - if (toplevelDir !== pkgKey) { - throw new PackageInvalidArchiveError( - `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` - ); - } - - const { name, version, description, type, categories, format_version: formatVersion } = manifest; - // check for mandatory fields - if (!(name && version && description && type && categories && formatVersion)) { - throw new PackageInvalidArchiveError( - 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' - ); - } - - const dataStreams = parseAndVerifyDataStreams(paths, name, version); - const policyTemplates = parseAndVerifyPolicyTemplates(manifest); - - return { - name, - version, - description, - type, - categories, - format_version: formatVersion, - data_streams: dataStreams, - policy_templates: policyTemplates, - }; -} - -function parseAndVerifyDataStreams( - paths: string[], - pkgName: string, - pkgVersion: string -): RegistryDataStream[] { - // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml - let dataStreamPaths: string[] = []; - const dataStreams: RegistryDataStream[] = []; - const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion }); - - // pick all paths matching name-version/data_stream/DATASTREAM_PATH/... - // from those, pick all unique data stream paths - paths - .filter((path) => path.startsWith(`${pkgKey}/data_stream/`)) - .forEach((path) => { - const parts = path.split('/'); - if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]); - }); - - dataStreamPaths = uniq(dataStreamPaths); - - dataStreamPaths.forEach((dataStreamPath) => { - const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`; - const manifestBuffer = cacheGet(manifestFile); - if (!paths.includes(manifestFile) || !manifestBuffer) { - throw new PackageInvalidArchiveError( - `No manifest.yml file found for data stream '${dataStreamPath}'` - ); - } - - let manifest; - try { - manifest = yaml.load(manifestBuffer.toString()); - } catch (error) { - throw new PackageInvalidArchiveError( - `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.` - ); - } - - const { - title: dataStreamTitle, - release, - ingest_pipeline: ingestPipeline, - type, - dataset, - } = manifest; - if (!(dataStreamTitle && release && type)) { - throw new PackageInvalidArchiveError( - `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'` - ); - } - const streams = parseAndVerifyStreams(manifest, dataStreamPath); - - // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26 - return dataStreams.push({ - dataset: dataset || `${pkgName}.${dataStreamPath}`, - title: dataStreamTitle, - release, - package: pkgName, - ingest_pipeline: ingestPipeline || 'default', - path: dataStreamPath, - type, - streams, - }); - }); - - return dataStreams; -} - -function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] { - const streams: RegistryStream[] = []; - const manifestStreams = manifest.streams; - if (manifestStreams && manifestStreams.length > 0) { - manifestStreams.forEach((manifestStream: any) => { - const { - input, - title: streamTitle, - description, - enabled, - vars: manifestVars, - template_path: templatePath, - } = manifestStream; - if (!(input && streamTitle)) { - throw new PackageInvalidArchiveError( - `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title` - ); - } - const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`); - // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143 - streams.push({ - input, - title: streamTitle, - description, - enabled, - vars, - template_path: templatePath || 'stream.yml.hbs', - }); - }); - } - return streams; -} - -function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] { - const vars: RegistryVarsEntry[] = []; - if (manifestVars && manifestVars.length > 0) { - manifestVars.forEach((manifestVar) => { - const { - name, - title: varTitle, - description, - type, - required, - show_user: showUser, - multi, - def, - os, - } = manifestVar; - if (!(name && type)) { - throw new PackageInvalidArchiveError( - `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}` - ); - } - vars.push({ - name, - title: varTitle, - description, - type, - required, - show_user: showUser, - multi, - default: def, - os, - }); - }); - } - return vars; -} - -function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] { - const policyTemplates: RegistryPolicyTemplate[] = []; - const manifestPolicyTemplates = manifest.policy_templates; - if (manifestPolicyTemplates && manifestPolicyTemplates > 0) { - manifestPolicyTemplates.forEach((policyTemplate: any) => { - const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate; - if (!(name && policyTemplateTitle && description && inputs)) { - throw new PackageInvalidArchiveError( - `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}` - ); - } - - const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`); - - // defaults to true if undefined, but may be explicitly set to false. - let parsedMultiple = true; - if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false; +export const deletePackageCache = (name: string, version: string) => { + // get cached archive filelist + const paths = getArchiveFilelist(name, version); - policyTemplates.push({ - name, - title: policyTemplateTitle, - description, - inputs: parsedInputs, - multiple: parsedMultiple, - }); - }); - } - return policyTemplates; -} + // delete cached archive filelist + deleteArchiveFilelist(name, version); -function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] { - const inputs: RegistryInput[] = []; - if (manifestInputs && manifestInputs.length > 0) { - manifestInputs.forEach((input: any) => { - const { type, title: inputTitle, description, vars } = input; - if (!(type && inputTitle)) { - throw new PackageInvalidArchiveError( - `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}` - ); - } - const parsedVars = parseAndVerifyVars(vars, location); - inputs.push({ - type, - title: inputTitle, - description, - vars: parsedVars, - }); - }); - } - return inputs; -} + // delete cached archive files + // this has been populated in unpackArchiveToCache() + paths?.forEach((path) => cacheDelete(path)); +}; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts new file mode 100644 index 00000000000000..90941aaf80cddc --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/archive/validation.ts @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import yaml from 'js-yaml'; +import { uniq } from 'lodash'; +import { + ArchivePackage, + RegistryPolicyTemplate, + RegistryDataStream, + RegistryInput, + RegistryStream, + RegistryVarsEntry, +} from '../../../../common/types'; +import { PackageInvalidArchiveError } from '../../../errors'; +import { pkgToPkgKey } from '../registry'; +import { cacheGet } from './cache'; + +// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the +// package registry. At some point this should probably be replaced (or enhanced) with verification based on +// https://github.com/elastic/package-spec/ +export function parseAndVerifyArchive(paths: string[]): ArchivePackage { + // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present + const toplevelDir = paths[0].split('/')[0]; + paths.forEach((path) => { + if (path.split('/')[0] !== toplevelDir) { + throw new PackageInvalidArchiveError('Package contains more than one top-level directory.'); + } + }); + + // The package must contain a manifest file ... + const manifestFile = `${toplevelDir}/manifest.yml`; + const manifestBuffer = cacheGet(manifestFile); + if (!paths.includes(manifestFile) || !manifestBuffer) { + throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.'); + } + + // ... which must be valid YAML + let manifest; + try { + manifest = yaml.load(manifestBuffer.toString()); + } catch (error) { + throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); + } + + // Package name and version from the manifest must match those from the toplevel directory + const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); + if (toplevelDir !== pkgKey) { + throw new PackageInvalidArchiveError( + `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` + ); + } + + const { name, version, description, type, categories, format_version: formatVersion } = manifest; + // check for mandatory fields + if (!(name && version && description && type && categories && formatVersion)) { + throw new PackageInvalidArchiveError( + 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' + ); + } + + const dataStreams = parseAndVerifyDataStreams(paths, name, version); + const policyTemplates = parseAndVerifyPolicyTemplates(manifest); + + return { + name, + version, + description, + type, + categories, + format_version: formatVersion, + data_streams: dataStreams, + policy_templates: policyTemplates, + }; +} +function parseAndVerifyDataStreams( + paths: string[], + pkgName: string, + pkgVersion: string +): RegistryDataStream[] { + // A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml + let dataStreamPaths: string[] = []; + const dataStreams: RegistryDataStream[] = []; + const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion }); + + // pick all paths matching name-version/data_stream/DATASTREAM_PATH/... + // from those, pick all unique data stream paths + paths + .filter((path) => path.startsWith(`${pkgKey}/data_stream/`)) + .forEach((path) => { + const parts = path.split('/'); + if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]); + }); + + dataStreamPaths = uniq(dataStreamPaths); + + dataStreamPaths.forEach((dataStreamPath) => { + const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`; + const manifestBuffer = cacheGet(manifestFile); + if (!paths.includes(manifestFile) || !manifestBuffer) { + throw new PackageInvalidArchiveError( + `No manifest.yml file found for data stream '${dataStreamPath}'` + ); + } + + let manifest; + try { + manifest = yaml.load(manifestBuffer.toString()); + } catch (error) { + throw new PackageInvalidArchiveError( + `Could not parse package manifest for data stream '${dataStreamPath}': ${error}.` + ); + } + + const { + title: dataStreamTitle, + release, + ingest_pipeline: ingestPipeline, + type, + dataset, + } = manifest; + if (!(dataStreamTitle && release && type)) { + throw new PackageInvalidArchiveError( + `Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'` + ); + } + const streams = parseAndVerifyStreams(manifest, dataStreamPath); + + // default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26 + return dataStreams.push({ + dataset: dataset || `${pkgName}.${dataStreamPath}`, + title: dataStreamTitle, + release, + package: pkgName, + ingest_pipeline: ingestPipeline || 'default', + path: dataStreamPath, + type, + streams, + }); + }); + + return dataStreams; +} +function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] { + const streams: RegistryStream[] = []; + const manifestStreams = manifest.streams; + if (manifestStreams && manifestStreams.length > 0) { + manifestStreams.forEach((manifestStream: any) => { + const { + input, + title: streamTitle, + description, + enabled, + vars: manifestVars, + template_path: templatePath, + } = manifestStream; + if (!(input && streamTitle)) { + throw new PackageInvalidArchiveError( + `Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title` + ); + } + const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`); + // default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143 + streams.push({ + input, + title: streamTitle, + description, + enabled, + vars, + template_path: templatePath || 'stream.yml.hbs', + }); + }); + } + return streams; +} +function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] { + const vars: RegistryVarsEntry[] = []; + if (manifestVars && manifestVars.length > 0) { + manifestVars.forEach((manifestVar) => { + const { + name, + title: varTitle, + description, + type, + required, + show_user: showUser, + multi, + def, + os, + } = manifestVar; + if (!(name && type)) { + throw new PackageInvalidArchiveError( + `Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}` + ); + } + vars.push({ + name, + title: varTitle, + description, + type, + required, + show_user: showUser, + multi, + default: def, + os, + }); + }); + } + return vars; +} +function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] { + const policyTemplates: RegistryPolicyTemplate[] = []; + const manifestPolicyTemplates = manifest.policy_templates; + if (manifestPolicyTemplates && manifestPolicyTemplates > 0) { + manifestPolicyTemplates.forEach((policyTemplate: any) => { + const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate; + if (!(name && policyTemplateTitle && description && inputs)) { + throw new PackageInvalidArchiveError( + `Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}` + ); + } + + const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`); + + // defaults to true if undefined, but may be explicitly set to false. + let parsedMultiple = true; + if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false; + + policyTemplates.push({ + name, + title: policyTemplateTitle, + description, + inputs: parsedInputs, + multiple: parsedMultiple, + }); + }); + } + return policyTemplates; +} +function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] { + const inputs: RegistryInput[] = []; + if (manifestInputs && manifestInputs.length > 0) { + manifestInputs.forEach((input: any) => { + const { type, title: inputTitle, description, vars } = input; + if (!(type && inputTitle)) { + throw new PackageInvalidArchiveError( + `Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}` + ); + } + const parsedVars = parseAndVerifyVars(vars, location); + inputs.push({ + type, + title: inputTitle, + description, + vars: parsedVars, + }); + }); + } + return inputs; +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts index abd2ba777e516b..dcd8846fa96a45 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts @@ -547,4 +547,77 @@ describe('processFields', () => { ]; expect(processFields(nested)).toEqual(nestedExpected); }); + + test('ignores redefinitions of a field', () => { + const fields = [ + { + name: 'a', + type: 'text', + }, + { + name: 'a', + type: 'number', + }, + { + name: 'b.c', + type: 'number', + }, + { + name: 'b', + type: 'group', + fields: [ + { + name: 'c', + type: 'text', + }, + ], + }, + ]; + + const fieldsExpected = [ + { + name: 'a', + // should preserve the field that was parsed first which had type: text + type: 'text', + }, + { + name: 'b', + type: 'group', + fields: [ + { + name: 'c', + // should preserve the field that was parsed first which had type: number + type: 'number', + }, + ], + }, + ]; + expect(processFields(fields)).toEqual(fieldsExpected); + }); + + test('ignores multiple redefinitions of a field', () => { + const fields = [ + { + name: 'a', + type: 'text', + }, + { + name: 'a', + type: 'number', + }, + { + name: 'a', + type: 'keyword', + }, + ]; + + const fieldsExpected = [ + { + name: 'a', + // should preserve the field that was parsed first which had type: text + type: 'text', + }, + ]; + expect(processFields(fields)).toEqual(fieldsExpected); + }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts index 5d3e8e9ce87d1b..b7650d10b6b250 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.test.ts @@ -61,11 +61,7 @@ describe('_installPackage', () => { const installationPromise = _installPackage({ savedObjectsClient: soClient, callCluster, - pkgName: 'abc', - pkgVersion: '1.2.3', paths: [], - removable: false, - internal: false, packageInfo: { name: 'xyz', version: '4.5.6', diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts index f570984cc61aac..a83d9428b7c939 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/_install_package.ts @@ -21,6 +21,7 @@ import { installPipelines, deletePreviousPipelines } from '../elasticsearch/inge import { installILMPolicy } from '../elasticsearch/ilm/install'; import { installKibanaAssets, getKibanaAssets } from '../kibana/assets/install'; import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; +import { isRequiredPackage } from './index'; import { deleteKibanaSavedObjectsAssets } from './remove'; import { installTransform } from '../elasticsearch/transform/install'; import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install'; @@ -32,28 +33,22 @@ import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './insta export async function _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo, installType, installSource, }: { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; - pkgName: string; - pkgVersion: string; installedPkg?: SavedObject; paths: string[]; - removable: boolean; - internal: boolean; packageInfo: InstallablePackage; installType: InstallType; installSource: InstallSource; }): Promise { + const { internal = false, name: pkgName, version: pkgVersion } = packageInfo; + const removable = !isRequiredPackage(pkgName); const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); // add the package installation to the saved object. // if some installation already exists, just update install info diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts index eb43bef72db703..ab93a73a55f39a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts @@ -6,9 +6,9 @@ import { InstallablePackage } from '../../../types'; import { getAssets } from './assets'; -import { getArchiveFilelist } from '../registry/cache'; +import { getArchiveFilelist } from '../archive/cache'; -jest.mock('../registry/cache', () => { +jest.mock('../archive/cache', () => { return { getArchiveFilelist: jest.fn(), }; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index 856f04c0c9b67b..2e2090312c9ae4 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -6,7 +6,7 @@ import { InstallablePackage } from '../../../types'; import * as Registry from '../registry'; -import { getArchiveFilelist } from '../registry/cache'; +import { getArchiveFilelist } from '../archive/cache'; // paths from RegistryPackage are routes to the assets on EPR // e.g. `/package/nginx/1.2.0/data_stream/access/fields/fields.yml` diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index 2021b353f1a279..893df1733c58bf 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -116,7 +116,7 @@ export async function getPackageInfo(options: { ] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), Registry.fetchFindLatestPackage(pkgName), - Registry.loadRegistryPackage(pkgName, pkgVersion), + Registry.getRegistryPackage(pkgName, pkgVersion), ]); // add properties that aren't (or aren't yet) on Registry response diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts index 410a9c0b225371..a1128011d81e6a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts @@ -29,8 +29,7 @@ export { BulkInstallResponse, IBulkInstallPackageError, handleInstallPackageFailure, - installPackageFromRegistry, - installPackageByUpload, + installPackage, ensureInstalledPackage, } from './install'; export { removeInstallation } from './remove'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 0496a6e9aeef1e..00a5c689e906d7 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -24,7 +24,6 @@ import * as Registry from '../registry'; import { getInstallation, getInstallationObject, - isRequiredPackage, bulkInstallPackages, isBulkInstallError, } from './index'; @@ -52,7 +51,7 @@ export async function installLatestPackage(options: { name: latestPackage.name, version: latestPackage.version, }); - return installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + return installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }); } catch (err) { throw err; } @@ -148,7 +147,8 @@ export async function handleInstallPackageFailure({ } const prevVersion = `${pkgName}-${installedPkg.attributes.version}`; logger.error(`rolling back to ${prevVersion} after error installing ${pkgkey}`); - await installPackageFromRegistry({ + await installPackage({ + installSource: 'registry', savedObjectsClient, pkgkey: prevVersion, callCluster, @@ -186,7 +186,12 @@ export async function upgradePackage({ }); try { - const assets = await installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster }); + const assets = await installPackage({ + installSource: 'registry', + savedObjectsClient, + pkgkey, + callCluster, + }); return { name: pkgToUpgrade, newVersion: latestPkg.version, @@ -218,19 +223,19 @@ export async function upgradePackage({ } } -interface InstallPackageParams { +interface InstallRegistryPackageParams { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; callCluster: CallESAsCurrentUser; force?: boolean; } -export async function installPackageFromRegistry({ +async function installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster, force = false, -}: InstallPackageParams): Promise { +}: InstallRegistryPackageParams): Promise { // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge @@ -248,39 +253,38 @@ export async function installPackageFromRegistry({ throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); } - const { paths, registryPackageInfo } = await Registry.loadRegistryPackage(pkgName, pkgVersion); - - const removable = !isRequiredPackage(pkgName); - const { internal = false } = registryPackageInfo; - const installSource = 'registry'; + const { paths, registryPackageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); return _installPackage({ savedObjectsClient, callCluster, - pkgName, - pkgVersion, installedPkg, paths, - removable, - internal, packageInfo: registryPackageInfo, installType, - installSource, + installSource: 'registry', }); } -export async function installPackageByUpload({ - savedObjectsClient, - callCluster, - archiveBuffer, - contentType, -}: { +interface InstallUploadedArchiveParams { savedObjectsClient: SavedObjectsClientContract; callCluster: CallESAsCurrentUser; archiveBuffer: Buffer; contentType: string; -}): Promise { +} + +export type InstallPackageParams = + | ({ installSource: Extract } & InstallRegistryPackageParams) + | ({ installSource: Extract } & InstallUploadedArchiveParams); + +async function installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, +}: InstallUploadedArchiveParams): Promise { const { paths, archivePackageInfo } = await loadArchivePackage({ archiveBuffer, contentType }); + const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName: archivePackageInfo.name, @@ -292,25 +296,45 @@ export async function installPackageByUpload({ ); } - const removable = !isRequiredPackage(archivePackageInfo.name); - const { internal = false } = archivePackageInfo; - const installSource = 'upload'; - return _installPackage({ savedObjectsClient, callCluster, - pkgName: archivePackageInfo.name, - pkgVersion: archivePackageInfo.version, installedPkg, paths, - removable, - internal, packageInfo: archivePackageInfo, installType, - installSource, + installSource: 'upload', }); } +export async function installPackage(args: InstallPackageParams) { + if (!('installSource' in args)) { + throw new Error('installSource is required'); + } + + if (args.installSource === 'registry') { + const { savedObjectsClient, pkgkey, callCluster, force } = args; + + return installPackageFromRegistry({ + savedObjectsClient, + pkgkey, + callCluster, + force, + }); + } else if (args.installSource === 'upload') { + const { savedObjectsClient, callCluster, archiveBuffer, contentType } = args; + + return installPackageByUpload({ + savedObjectsClient, + callCluster, + archiveBuffer, + contentType, + }); + } + // @ts-expect-error s/b impossibe b/c `never` by this point, but just in case + throw new Error(`Unknown installSource: ${args.installSource}`); +} + export const updateVersion = async ( savedObjectsClient: SavedObjectsClientContract, pkgName: string, @@ -421,7 +445,9 @@ export async function ensurePackagesCompletedInstall( const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; // reinstall package if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { - acc.push(installPackageFromRegistry({ savedObjectsClient, pkgkey, callCluster })); + acc.push( + installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, callCluster }) + ); } return acc; }, []); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 5db47adc983c2a..9fabbaf72474e2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -21,7 +21,8 @@ import { deletePipeline } from '../elasticsearch/ingest_pipeline/'; import { installIndexPatterns } from '../kibana/index_pattern/install'; import { deleteTransforms } from '../elasticsearch/transform/remove'; import { packagePolicyService, appContextService } from '../..'; -import { splitPkgKey, deletePackageCache } from '../registry'; +import { splitPkgKey } from '../registry'; +import { deletePackageCache } from '../archive'; export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index e6d14a7846c225..52a1894570b2a2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import mime from 'mime-types'; import semver from 'semver'; import { Response } from 'node-fetch'; import { URL } from 'url'; @@ -18,15 +18,8 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; -import { - cacheGet, - cacheSet, - cacheDelete, - getArchiveFilelist, - setArchiveFilelist, - deleteArchiveFilelist, -} from './cache'; -import { ArchiveEntry, getBufferExtractor } from './extract'; +import { unpackArchiveToCache } from '../archive'; +import { cacheGet, getArchiveFilelist, setArchiveFilelist } from '../archive'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; import { getRegistryUrl } from './registry_url'; @@ -132,37 +125,18 @@ export async function fetchCategories(params?: CategoriesParams): Promise true -): Promise { - const paths: string[] = []; - const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); - const bufferExtractor = getBufferExtractor({ archivePath }); - if (!bufferExtractor) { - throw new Error('Unknown compression format. Please use .zip or .gz'); - } - await bufferExtractor(archiveBuffer, filter, (entry: ArchiveEntry) => { - const { path, buffer } = entry; - const { file } = pathParts(path); - if (!file) return; - if (buffer) { - cacheSet(path, buffer); - paths.push(path); - } - }); - - return paths; -} - -export async function loadRegistryPackage( +export async function getRegistryPackage( pkgName: string, pkgVersion: string ): Promise<{ paths: string[]; registryPackageInfo: RegistryPackage }> { let paths = getArchiveFilelist(pkgName, pkgVersion); if (!paths || paths.length === 0) { - paths = await unpackRegistryPackageToCache(pkgName, pkgVersion); + const { archiveBuffer, archivePath } = await fetchArchiveBuffer(pkgName, pkgVersion); + const contentType = mime.lookup(archivePath); + if (!contentType) { + throw new Error(`Unknown compression format for '${archivePath}'. Please use .zip or .gz`); + } + paths = await unpackArchiveToCache(archiveBuffer, contentType); setArchiveFilelist(pkgName, pkgVersion, paths); } @@ -210,7 +184,7 @@ export async function ensureCachedArchiveInfo( const paths = getArchiveFilelist(name, version); if (!paths || paths.length === 0) { if (installSource === 'registry') { - await loadRegistryPackage(name, version); + await getRegistryPackage(name, version); } else { throw new PackageCacheError( `Package ${name}-${version} not cached. If it was uploaded, try uninstalling and reinstalling manually.` @@ -257,15 +231,3 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy // elasticsearch: assets.elasticsearch, }; } - -export const deletePackageCache = (name: string, version: string) => { - // get cached archive filelist - const paths = getArchiveFilelist(name, version); - - // delete cached archive filelist - deleteArchiveFilelist(name, version); - - // delete cached archive files - // this has been populated in unpackRegistryPackageToCache() - paths?.forEach((path) => cacheDelete(path)); -}; diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts index 6064e5bae06348..6ae76c56436d57 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.test.ts @@ -34,6 +34,12 @@ jest.mock('./epm/packages/assets', () => { }; }); +jest.mock('./epm/packages', () => { + return { + getPackageInfo: () => ({}), + }; +}); + jest.mock('./epm/registry', () => { return { fetchInfo: () => ({}), diff --git a/x-pack/plugins/ingest_manager/server/services/package_policy.ts b/x-pack/plugins/ingest_manager/server/services/package_policy.ts index dc3a4495191c96..0f78c97a6f2bdc 100644 --- a/x-pack/plugins/ingest_manager/server/services/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/package_policy.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsClientContract } from 'src/core/server'; +import uuid from 'uuid'; import { AuthenticatedUser } from '../../../security/server'; import { DeletePackagePoliciesResponse, PackagePolicyInput, + NewPackagePolicyInput, PackagePolicyInputStream, PackageInfo, ListWithKuery, @@ -58,6 +60,11 @@ class PackagePolicyService { throw new Error('There is already a package with the same name on this agent policy'); } } + // Add ids to stream + const packagePolicyId = options?.id || uuid.v4(); + let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) => + assignStreamIdToInput(packagePolicyId, input) + ); // Make sure the associated package is installed if (packagePolicy.package?.name) { @@ -85,7 +92,7 @@ class PackagePolicyService { } } - packagePolicy.inputs = await this.assignPackageStream(pkgInfo, packagePolicy.inputs); + inputs = await this.assignPackageStream(pkgInfo, inputs); } const isoDate = new Date().toISOString(); @@ -93,13 +100,15 @@ class PackagePolicyService { SAVED_OBJECT_TYPE, { ...packagePolicy, + inputs, revision: 1, created_at: isoDate, created_by: options?.user?.username ?? 'system', updated_at: isoDate, updated_by: options?.user?.username ?? 'system', }, - options + + { ...options, id: packagePolicyId } ); // Assign it to the given agent policy @@ -124,18 +133,28 @@ class PackagePolicyService { const isoDate = new Date().toISOString(); // eslint-disable-next-line @typescript-eslint/naming-convention const { saved_objects } = await soClient.bulkCreate( - packagePolicies.map((packagePolicy) => ({ - type: SAVED_OBJECT_TYPE, - attributes: { - ...packagePolicy, - policy_id: agentPolicyId, - revision: 1, - created_at: isoDate, - created_by: options?.user?.username ?? 'system', - updated_at: isoDate, - updated_by: options?.user?.username ?? 'system', - }, - })) + packagePolicies.map((packagePolicy) => { + const packagePolicyId = uuid.v4(); + + const inputs = packagePolicy.inputs.map((input) => + assignStreamIdToInput(packagePolicyId, input) + ); + + return { + type: SAVED_OBJECT_TYPE, + id: packagePolicyId, + attributes: { + ...packagePolicy, + inputs, + policy_id: agentPolicyId, + revision: 1, + created_at: isoDate, + created_by: options?.user?.username ?? 'system', + updated_at: isoDate, + updated_by: options?.user?.username ?? 'system', + }, + }; + }) ); // Filter out invalid SOs @@ -255,11 +274,26 @@ class PackagePolicyService { } } + let inputs = await restOfPackagePolicy.inputs.map((input) => + assignStreamIdToInput(oldPackagePolicy.id, input) + ); + + if (packagePolicy.package?.name) { + const pkgInfo = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + }); + + inputs = await this.assignPackageStream(pkgInfo, inputs); + } + await soClient.update( SAVED_OBJECT_TYPE, id, { ...restOfPackagePolicy, + inputs, revision: oldPackagePolicy.revision + 1, updated_at: new Date().toISOString(), updated_by: options?.user?.username ?? 'system', @@ -353,6 +387,15 @@ class PackagePolicyService { } } +function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyInput) { + return { + ...input, + streams: input.streams.map((stream) => { + return { ...stream, id: `${input.type}-${stream.data_stream.dataset}-${packagePolicyId}` }; + }), + }; +} + async function _assignPackageStreamToInput( registryPkgInfo: RegistryPackage, pkgInfo: PackageInfo, diff --git a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts index 6673c12d515117..20d29c0aa18c9a 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/package_policy.ts @@ -54,7 +54,7 @@ const PackagePolicyBaseSchema = { ), streams: schema.arrayOf( schema.object({ - id: schema.string(), + id: schema.maybe(schema.string()), // BWC < 7.11 enabled: schema.boolean(), data_stream: schema.object({ dataset: schema.string(), type: schema.string() }), vars: schema.maybe(ConfigRecordSchema), diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 2da67f81122ab1..ce78757676bccb 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -19,5 +19,5 @@ "optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], - "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable"] + "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "lensOss"] } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 82f753e3520b05..c9d99bcfb6d8d8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -93,6 +93,7 @@ export function LayerPanel( state: props.visualizationState, frame: props.framePublicAPI, dateRange: props.framePublicAPI.dateRange, + activeData: props.framePublicAPI.activeData, }; const datasourceId = datasourcePublicAPI.datasourceId; const layerDatasourceState = props.datasourceStates[datasourceId].state; @@ -111,6 +112,7 @@ export function LayerPanel( ...layerDatasourceDropProps, frame: props.framePublicAPI, dateRange: props.framePublicAPI.dateRange, + activeData: props.framePublicAPI.activeData, }; const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps); @@ -140,6 +142,7 @@ export function LayerPanel( nativeProps={{ layerId, state: layerDatasourceState, + activeData: props.framePublicAPI.activeData, setState: (updater: unknown) => { const newState = typeof updater === 'function' ? updater(layerDatasourceState) : updater; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index bb40b9f31d2546..935d65bfb6c08b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -101,6 +101,7 @@ export function EditorFrame(props: EditorFrameProps) { const framePublicAPI: FramePublicAPI = { datasourceLayers, + activeData: state.activeData, dateRange: props.dateRange, query: props.query, filters: props.filters, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts index fc8daaed059ddf..e0101493b27aab 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts @@ -6,6 +6,7 @@ import { EditorFrameProps } from './index'; import { Document } from '../../persistence/saved_object_store'; +import { TableInspectorAdapter } from '../types'; export interface PreviewState { visualization: { @@ -21,6 +22,7 @@ export interface EditorFrameState extends PreviewState { description?: string; stagedPreview?: PreviewState; activeDatasourceId: string | null; + activeData?: TableInspectorAdapter; } export type Action = @@ -32,6 +34,10 @@ export type Action = type: 'UPDATE_TITLE'; title: string; } + | { + type: 'UPDATE_ACTIVE_DATA'; + tables: TableInspectorAdapter; + } | { type: 'UPDATE_STATE'; // Just for diagnostics, so we can determine what action @@ -139,6 +145,11 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta return { ...state, title: action.title }; case 'UPDATE_STATE': return action.updater(state); + case 'UPDATE_ACTIVE_DATA': + return { + ...state, + activeData: action.tables, + }; case 'UPDATE_LAYER': return { ...state, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 95057f9db7e931..daaf893f2a703d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -7,6 +7,7 @@ import _ from 'lodash'; import { Ast } from '@kbn/interpreter/common'; import { IconType } from '@elastic/eui/src/components/icon/icon'; +import { Datatable } from 'src/plugins/expressions'; import { PaletteOutput } from 'src/plugins/charts/public'; import { VisualizeFieldContext } from '../../../../../../src/plugins/ui_actions/public'; import { @@ -50,6 +51,7 @@ export function getSuggestions({ visualizationState, field, visualizeTriggerFieldContext, + activeData, mainPalette, }: { datasourceMap: Record; @@ -66,6 +68,7 @@ export function getSuggestions({ visualizationState: unknown; field?: unknown; visualizeTriggerFieldContext?: VisualizeFieldContext; + activeData?: Record; mainPalette?: PaletteOutput; }): Suggestion[] { const datasources = Object.entries(datasourceMap).filter( @@ -87,7 +90,8 @@ export function getSuggestions({ dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field); } else { dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( - datasourceState + datasourceState, + activeData ); } return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId })); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 201c91ee916764..2e24b64ecca26a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -188,6 +188,7 @@ export function SuggestionPanel({ visualizationMap, activeVisualizationId: currentVisualizationId, visualizationState: currentVisualizationState, + activeData: frame.activeData, }) .filter((suggestion) => !suggestion.hide) .filter( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index fe8747de667a3d..659626149aef26 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -325,6 +325,7 @@ function getTopSuggestion( activeVisualizationId: props.visualizationId, visualizationState: props.visualizationState, subVisualizationId, + activeData: props.framePublicAPI.activeData, mainPalette, }); const suggestions = unfilteredSuggestions.filter((suggestion) => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 447e94d09cdb2f..231c38ea540484 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -253,6 +253,48 @@ describe('workspace_panel', () => { expect(trigger.exec).toHaveBeenCalledWith({ data: eventData }); }); + it('should push add current data table to state on data$ emitting value', () => { + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + mockDatasource.toExpression.mockReturnValue('datasource'); + mockDatasource.getLayers.mockReturnValue(['first']); + const dispatch = jest.fn(); + + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={dispatch} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + plugins={{ uiActions: uiActionsMock, data: dataMock }} + /> + ); + + const onData = expressionRendererMock.mock.calls[0][0].onData$!; + + const tableData = { table1: { columns: [], rows: [] } }; + onData(undefined, { tables: tableData }); + + expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_ACTIVE_DATA', tables: tableData }); + }); + it('should include data fetching for each layer in the expression', () => { const mockDatasource2 = createMockDatasource('a'); const framePublicAPI = createMockFramePublicAPI(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 5a6e9af5d6ff2e..e0dd3b3fe01ae9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -50,6 +50,7 @@ import { } from '../../../../../../../src/plugins/data/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; +import { LensInspectorAdapters } from '../../types'; import { getOriginalRequestErrorMessage } from '../../error_helper'; import { validateDatasourceAndVisualization } from '../state_helpers'; @@ -296,6 +297,7 @@ export function WorkspacePanel({ expression={expression} framePublicAPI={framePublicAPI} timefilter={plugins.data.query.timefilter.timefilter} + dispatch={dispatch} onEvent={onEvent} setLocalState={setLocalState} localState={{ ...localState, configurationValidationError }} @@ -339,11 +341,13 @@ export const InnerVisualizationWrapper = ({ setLocalState, localState, ExpressionRendererComponent, + dispatch, }: { expression: Ast | null | undefined; framePublicAPI: FramePublicAPI; timefilter: TimefilterContract; onEvent: (event: ExpressionRendererEvent) => void; + dispatch: (action: Action) => void; setLocalState: (dispatch: (prevState: WorkspaceState) => WorkspaceState) => void; localState: WorkspaceState & { configurationValidationError?: Array<{ shortMessage: string; longMessage: string }>; @@ -369,6 +373,18 @@ export const InnerVisualizationWrapper = ({ ] ); + const onData$ = useCallback( + (data: unknown, inspectorAdapters?: LensInspectorAdapters) => { + if (inspectorAdapters && inspectorAdapters.tables) { + dispatch({ + type: 'UPDATE_ACTIVE_DATA', + tables: inspectorAdapters.tables, + }); + } + }, + [dispatch] + ); + if (localState.configurationValidationError) { let showExtraErrors = null; if (localState.configurationValidationError.length > 1) { @@ -455,6 +471,7 @@ export const InnerVisualizationWrapper = ({ searchContext={context} reload$={autoRefreshFetch$} onEvent={onEvent} + onData$={onData$} renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => { const visibleErrorMessage = getOriginalRequestErrorMessage(error) || errorMessage; diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts index 5afabb9a52367a..07c16665d11b44 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts @@ -6,34 +6,35 @@ import moment from 'moment'; import { mergeTables } from './merge_tables'; -import { Datatable } from 'src/plugins/expressions'; +import { Datatable, ExecutionContext } from 'src/plugins/expressions'; +import { LensInspectorAdapters } from './types'; describe('lens_merge_tables', () => { - it('should produce a row with the nested table as defined', () => { - const sampleTable1: Datatable = { - type: 'datatable', - columns: [ - { id: 'bucket', name: 'A', meta: { type: 'string' } }, - { id: 'count', name: 'Count', meta: { type: 'number' } }, - ], - rows: [ - { bucket: 'a', count: 5 }, - { bucket: 'b', count: 10 }, - ], - }; + const sampleTable1: Datatable = { + type: 'datatable', + columns: [ + { id: 'bucket', name: 'A', meta: { type: 'string' } }, + { id: 'count', name: 'Count', meta: { type: 'number' } }, + ], + rows: [ + { bucket: 'a', count: 5 }, + { bucket: 'b', count: 10 }, + ], + }; - const sampleTable2: Datatable = { - type: 'datatable', - columns: [ - { id: 'bucket', name: 'C', meta: { type: 'string' } }, - { id: 'avg', name: 'Average', meta: { type: 'number' } }, - ], - rows: [ - { bucket: 'a', avg: 2.5 }, - { bucket: 'b', avg: 9 }, - ], - }; + const sampleTable2: Datatable = { + type: 'datatable', + columns: [ + { id: 'bucket', name: 'C', meta: { type: 'string' } }, + { id: 'avg', name: 'Average', meta: { type: 'number' } }, + ], + rows: [ + { bucket: 'a', avg: 2.5 }, + { bucket: 'b', avg: 9 }, + ], + }; + it('should produce a row with the nested table as defined', () => { expect( mergeTables.fn( null, @@ -47,6 +48,15 @@ describe('lens_merge_tables', () => { }); }); + it('should store the current tables in the tables inspector', () => { + const adapters: LensInspectorAdapters = { tables: {} }; + mergeTables.fn(null, { layerIds: ['first', 'second'], tables: [sampleTable1, sampleTable2] }, { + inspectorAdapters: adapters, + } as ExecutionContext); + expect(adapters.tables!.first).toBe(sampleTable1); + expect(adapters.tables!.second).toBe(sampleTable2); + }); + it('should pass the date range along', () => { expect( mergeTables.fn( diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts index e4f7b07084ea97..03ef7cf9cc6374 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { + ExecutionContext, Datatable, ExpressionFunctionDefinition, ExpressionValueSearchContext, @@ -14,6 +15,7 @@ import { search } from '../../../../../src/plugins/data/public'; const { toAbsoluteDates } = search.aggs; import { LensMultiTable } from '../types'; +import { LensInspectorAdapters } from './types'; interface MergeTables { layerIds: string[]; @@ -24,12 +26,14 @@ export const mergeTables: ExpressionFunctionDefinition< 'lens_merge_tables', ExpressionValueSearchContext | null, MergeTables, - LensMultiTable + LensMultiTable, + ExecutionContext > = { name: 'lens_merge_tables', type: 'lens_multitable', help: i18n.translate('xpack.lens.functions.mergeTables.help', { - defaultMessage: 'A helper to merge any number of kibana tables into a single table', + defaultMessage: + 'A helper to merge any number of kibana tables into a single table and expose it via inspector adapter', }), args: { layerIds: { @@ -44,10 +48,18 @@ export const mergeTables: ExpressionFunctionDefinition< }, }, inputTypes: ['kibana_context', 'null'], - fn(input, { layerIds, tables }) { + fn(input, { layerIds, tables }, context) { + if (!context.inspectorAdapters) { + context.inspectorAdapters = {}; + } + if (!context.inspectorAdapters.tables) { + context.inspectorAdapters.tables = {}; + } const resultTables: Record = {}; tables.forEach((table, index) => { resultTables[layerIds[index]] = table; + // adapter is always defined at that point because we make sure by the beginning of the function + context.inspectorAdapters.tables![layerIds[index]] = table; }); return { type: 'lens_multitable', diff --git a/x-pack/plugins/lens/public/editor_frame_service/types.ts b/x-pack/plugins/lens/public/editor_frame_service/types.ts new file mode 100644 index 00000000000000..2da95ec2fd66f6 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/types.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 { Datatable } from 'src/plugins/expressions'; + +export type TableInspectorAdapter = Record; +export interface LensInspectorAdapters { + tables?: TableInspectorAdapter; +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 35987656f66703..92280b0fb6ce6f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -33,19 +33,23 @@ export class IndexPatternDatasource { { expressions, editorFrame, charts }: IndexPatternDatasourceSetupPlugins ) { editorFrame.registerDatasource(async () => { - const { getIndexPatternDatasource, renameColumns, formatColumn } = await import( - '../async_services' - ); - expressions.registerFunction(renameColumns); - expressions.registerFunction(formatColumn); - return core.getStartServices().then(([coreStart, { data }]) => - getIndexPatternDatasource({ + const { + getIndexPatternDatasource, + renameColumns, + formatColumn, + getTimeScaleFunction, + } = await import('../async_services'); + return core.getStartServices().then(([coreStart, { data }]) => { + expressions.registerFunction(getTimeScaleFunction(data)); + expressions.registerFunction(renameColumns); + expressions.registerFunction(formatColumn); + return getIndexPatternDatasource({ core: coreStart, storage: new Storage(localStorage), data, charts, - }) - ) as Promise; + }); + }) as Promise; }); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 0d822927808084..ecca1b878e9a7d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -76,6 +76,7 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri export * from './rename_columns'; export * from './format_column'; +export * from './time_scale'; export function getIndexPatternDatasource({ core, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 85deb2bac25cae..dcb4646816e135 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -34,6 +34,13 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { size: number; orderBy: { type: 'alphabetical' } | { type: 'column'; columnId: string }; orderDirection: 'asc' | 'desc'; + // Terms on numeric fields can be formatted + format?: { + id: string; + params?: { + decimals: number; + }; + }; }; } @@ -105,10 +112,16 @@ export const termsOperation: OperationDefinition { + const newParams = { ...oldColumn.params }; + if ('format' in newParams && field.type !== 'number') { + delete newParams.format; + } return { ...oldColumn, + dataType: field.type as DataType, label: ofName(field.displayName), sourceField: field.name, + params: newParams, }; }, onOtherColumnChanged: (currentColumn, columns) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 2c4e67ef0d9b99..1341ca0587c754 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -103,14 +103,40 @@ describe('terms', () => { }, }; const indexPattern = createMockedIndexPattern(); - const newDateField = indexPattern.fields.find((i) => i.name === 'dest')!; + const newNumberField = indexPattern.fields.find((i) => i.name === 'bytes')!; - const column = termsOperation.onFieldChange(oldColumn, indexPattern, newDateField); - expect(column).toHaveProperty('sourceField', 'dest'); + const column = termsOperation.onFieldChange(oldColumn, indexPattern, newNumberField); + expect(column).toHaveProperty('dataType', 'number'); + expect(column).toHaveProperty('sourceField', 'bytes'); expect(column).toHaveProperty('params.size', 5); expect(column).toHaveProperty('params.orderBy.type', 'alphabetical'); expect(column).toHaveProperty('params.orderDirection', 'asc'); - expect(column.label).toContain('dest'); + expect(column.label).toContain('bytes'); + }); + + it('should remove numeric parameters when changing away from number', () => { + const oldColumn: TermsIndexPatternColumn = { + operationType: 'terms', + sourceField: 'bytes', + label: 'Top values of bytes', + isBucketed: true, + dataType: 'number', + params: { + size: 5, + orderBy: { + type: 'alphabetical', + }, + orderDirection: 'asc', + format: { id: 'number', params: { decimals: 0 } }, + }, + }; + const indexPattern = createMockedIndexPattern(); + const newStringField = indexPattern.fields.find((i) => i.name === 'source')!; + + const column = termsOperation.onFieldChange(oldColumn, indexPattern, newStringField); + expect(column).toHaveProperty('dataType', 'string'); + expect(column).toHaveProperty('sourceField', 'source'); + expect(column.params.format).toBeUndefined(); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts new file mode 100644 index 00000000000000..c29e2cd9567dc8 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts @@ -0,0 +1,368 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { Datatable } from 'src/plugins/expressions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; +import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; +import { getTimeScaleFunction, TimeScaleArgs } from './time_scale'; + +describe('time_scale', () => { + let timeScale: (input: Datatable, args: TimeScaleArgs) => Promise; + let dataMock: jest.Mocked; + + const emptyTable: Datatable = { + type: 'datatable', + columns: [ + { + id: 'date', + name: 'date', + meta: { + type: 'date', + }, + }, + { + id: 'metric', + name: 'metric', + meta: { + type: 'number', + }, + }, + ], + rows: [], + }; + + const defaultArgs: TimeScaleArgs = { + dateColumnId: 'date', + inputColumnId: 'metric', + outputColumnId: 'scaledMetric', + targetUnit: 'h', + }; + + beforeEach(() => { + dataMock = dataPluginMock.createStartContract(); + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T00:00:00.000Z', + to: '2020-10-10T00:00:00.000Z', + }, + interval: '1d', + }); + (dataMock.query.timefilter.timefilter.calculateBounds as jest.Mock).mockImplementation( + ({ from, to }) => ({ + min: moment(from), + max: moment(to), + }) + ); + timeScale = functionWrapper(getTimeScaleFunction(dataMock)); + }); + + it('should apply time scale factor to each row', async () => { + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); + + it('should skip gaps in the data', async () => { + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([ + 1, + undefined, + undefined, + 1, + 1, + ]); + }); + + it('should return input unchanged if input column does not exist', async () => { + const mismatchedTable = { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 24, + }, + ], + }; + const result = await timeScale(mismatchedTable, { + ...defaultArgs, + inputColumnId: 'nonexistent', + }); + + expect(result).toBe(mismatchedTable); + }); + + it('should be able to scale up as well', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T12:00:00.000Z', + to: '2020-10-05T16:00:00.000Z', + }, + interval: '1h', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-05T12:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T13:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T14:00:00.000Z').valueOf(), + metric: 1, + }, + { + date: moment('2020-10-05T15:00:00.000Z').valueOf(), + metric: 1, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'd', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([24, 24, 24, 24]); + }); + + it('can scale starting from unit multiple target intervals', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T13:00:00.000Z', + to: '2020-10-05T23:00:00.000Z', + }, + interval: '3h', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + // bucket is cut off by one hour because of the time range + date: moment('2020-10-05T12:00:00.000Z').valueOf(), + metric: 2, + }, + { + date: moment('2020-10-05T15:00:00.000Z').valueOf(), + metric: 3, + }, + { + date: moment('2020-10-05T18:00:00.000Z').valueOf(), + metric: 3, + }, + { + // bucket is cut off by one hour because of the time range + date: moment('2020-10-05T21:00:00.000Z').valueOf(), + metric: 2, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'h', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]); + }); + + it('take start and end of timerange into account', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2020-10-05T12:00:00.000Z', + to: '2020-10-09T12:00:00.000Z', + }, + interval: '1d', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + // this is a partial bucket because it starts before the start of the time range + date: moment('2020-10-05T00:00:00.000Z').valueOf(), + metric: 12, + }, + { + date: moment('2020-10-06T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-07T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-08T00:00:00.000Z').valueOf(), + metric: 24, + }, + { + // this is a partial bucket because it ends earlier than the regular interval of 1d + date: moment('2020-10-09T00:00:00.000Z').valueOf(), + metric: 12, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); + + it('should respect DST switches', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'Europe/Berlin', + timeRange: { + from: '2020-10-23T00:00:00.000+02:00', + to: '2020-10-27T00:00:00.000+01:00', + }, + interval: '1d', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2020-10-23T00:00:00.000+02:00').valueOf(), + metric: 24, + }, + { + date: moment('2020-10-24T00:00:00.000+02:00').valueOf(), + metric: 24, + }, + { + // this day has one hour more in Europe/Berlin due to DST switch + date: moment('2020-10-25T00:00:00.000+02:00').valueOf(), + metric: 25, + }, + { + date: moment('2020-10-26T00:00:00.000+01:00').valueOf(), + metric: 24, + }, + ], + }, + { + ...defaultArgs, + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1]); + }); + + it('take leap years into account', async () => { + (dataMock.search.aggs.getDateMetaByDatatableColumn as jest.Mock).mockReturnValue({ + timeZone: 'UTC', + timeRange: { + from: '2010-01-01T00:00:00.000Z', + to: '2015-01-01T00:00:00.000Z', + }, + interval: '1y', + }); + const result = await timeScale( + { + ...emptyTable, + rows: [ + { + date: moment('2010-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + date: moment('2011-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + // 2012 is a leap year and has an additional day + date: moment('2012-01-01T00:00:00.000Z').valueOf(), + metric: 366, + }, + { + date: moment('2013-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + { + date: moment('2014-01-01T00:00:00.000Z').valueOf(), + metric: 365, + }, + ], + }, + { + ...defaultArgs, + targetUnit: 'd', + } + ); + + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([1, 1, 1, 1, 1]); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts new file mode 100644 index 00000000000000..0937f40eeb6d3d --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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-timezone'; +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { search } from '../../../../../src/plugins/data/public'; + +type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; + +export interface TimeScaleArgs { + dateColumnId: string; + inputColumnId: string; + outputColumnId: string; + targetUnit: TimeScaleUnit; + outputColumnName?: string; +} + +const unitInMs: Record = { + s: 1000, + m: 1000 * 60, + h: 1000 * 60 * 60, + d: 1000 * 60 * 60 * 24, +}; + +export function getTimeScaleFunction(data: DataPublicPluginStart) { + const timeScale: ExpressionFunctionDefinition< + 'lens_time_scale', + Datatable, + TimeScaleArgs, + Promise + > = { + name: 'lens_time_scale', + type: 'datatable', + help: '', + args: { + dateColumnId: { + types: ['string'], + help: '', + required: true, + }, + inputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnName: { + types: ['string'], + help: '', + }, + targetUnit: { + types: ['string'], + options: ['s', 'm', 'h', 'd'], + help: '', + required: true, + }, + }, + inputTypes: ['datatable'], + async fn( + input, + { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs + ) { + if (input.columns.some((column) => column.id === outputColumnId)) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.columnConflictMessage', { + defaultMessage: 'Specified outputColumnId {columnId} already exists.', + values: { + columnId: outputColumnId, + }, + }) + ); + } + + const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); + + if (!dateColumnDefinition) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { + defaultMessage: 'Specified dateColumnId {columnId} does not exist.', + values: { + columnId: dateColumnId, + }, + }) + ); + } + + const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + + if (!inputColumnDefinition) { + return input; + } + + const outputColumnDefinition = { + ...inputColumnDefinition, + id: outputColumnId, + name: outputColumnName || outputColumnId, + }; + + const resultColumns = [...input.columns]; + // add output column after input column in the table + resultColumns.splice( + resultColumns.indexOf(inputColumnDefinition) + 1, + 0, + outputColumnDefinition + ); + + const targetUnitInMs = unitInMs[targetUnit]; + const timeInfo = await data.search.aggs.getDateMetaByDatatableColumn(dateColumnDefinition); + const intervalDuration = timeInfo && search.aggs.parseInterval(timeInfo.interval); + + if (!timeInfo || !intervalDuration) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { + defaultMessage: 'Could not fetch date histogram information', + }) + ); + } + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeInfo.timeZone); + + const timeBounds = + timeInfo.timeRange && data.query.timefilter.timefilter.calculateBounds(timeInfo.timeRange); + + const result = { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + let startOfBucket = moment(row[dateColumnId]); + let endOfBucket = startOfBucket.clone().add(intervalDuration); + if (timeBounds && timeBounds.min) { + startOfBucket = moment.max(startOfBucket, timeBounds.min); + } + if (timeBounds && timeBounds.max) { + endOfBucket = moment.min(endOfBucket, timeBounds.max); + } + const bucketSize = endOfBucket.diff(startOfBucket); + const factor = bucketSize / targetUnitInMs; + + const currentValue = newRow[inputColumnId]; + if (currentValue != null) { + newRow[outputColumnId] = Number(currentValue) / factor; + } + + return newRow; + }), + }; + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + + return result; + }, + }; + return timeScale; +} diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 36237eeb6b05f0..2f9310ee24ae9a 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -9,7 +9,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { DashboardStart } from 'src/plugins/dashboard/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; -import { VisualizationsSetup } from 'src/plugins/visualizations/public'; +import { VisualizationsSetup, VisualizationsStart } from 'src/plugins/visualizations/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { UrlForwardingSetup } from 'src/plugins/url_forwarding/public'; import { GlobalSearchPluginSetup } from '../../global_search/public'; @@ -35,6 +35,7 @@ import { VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; +import { PLUGIN_ID_OSS } from '../../../../src/plugins/lens_oss/common/constants'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; @@ -58,6 +59,7 @@ export interface LensPluginStartDependencies { navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; dashboard: DashboardStart; + visualizations: VisualizationsStart; embeddable: EmbeddableStart; charts: ChartsPluginStart; savedObjectsTagging?: SavedObjectTaggingPluginStart; @@ -170,6 +172,8 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies) { this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance; + // unregisters the OSS alias + startDependencies.visualizations.unRegisterAlias(PLUGIN_ID_OSS); // unregisters the Visualize action and registers the lens one if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx index 679f3a44bb60ed..20837424dc7b59 100644 --- a/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx +++ b/x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx @@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend'; const typeToIconMap: { [type: string]: string | IconType } = { legend: EuiIconLegend as IconType, - values: 'visText', + values: 'number', }; export interface ToolbarPopoverProps { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 27ab8f258bba85..3c96579fdc9431 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -178,7 +178,10 @@ export interface Datasource { indexPatternId: string, fieldName: string ) => Array>; - getDatasourceSuggestionsFromCurrentState: (state: T) => Array>; + getDatasourceSuggestionsFromCurrentState: ( + state: T, + activeData?: Record + ) => Array>; getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI; getErrorMessages: (state: T) => Array<{ shortMessage: string; longMessage: string }> | undefined; @@ -231,6 +234,7 @@ export type DatasourceDimensionProps = SharedDimensionProps & { columnId: string; onRemove?: (accessor: string) => void; state: T; + activeData?: Record; }; // The only way a visualization has to restrict the query building @@ -249,6 +253,7 @@ export interface DatasourceLayerPanelProps { layerId: string; state: T; setState: StateSetter; + activeData?: Record; } export interface DraggedOperation { @@ -428,6 +433,12 @@ export interface VisualizationSuggestion { export interface FramePublicAPI { datasourceLayers: Record; + /** + * Data of the chart currently rendered in the preview. + * This data might be not available (e.g. if the chart can't be rendered) or outdated and belonging to another chart. + * If accessing, make sure to check whether expected columns actually exist. + */ + activeData?: Record; dateRange: DateRange; query: Query; diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts index d0dceed03db2fe..e20dcfacd5ab5d 100644 --- a/x-pack/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -12,19 +12,16 @@ export const getLensAliasConfig = (): VisTypeAlias => ({ aliasPath: getBasePath(), aliasApp: 'lens', name: 'lens', - promotion: { - description: i18n.translate('xpack.lens.visTypeAlias.promotion.description', { - defaultMessage: 'Try Lens, our new, intuitive way to create visualizations.', - }), - buttonText: i18n.translate('xpack.lens.visTypeAlias.promotion.buttonText', { - defaultMessage: 'Go to Lens', - }), - }, + promotion: true, title: i18n.translate('xpack.lens.visTypeAlias.title', { defaultMessage: 'Lens', }), description: i18n.translate('xpack.lens.visTypeAlias.description', { - defaultMessage: `Lens is a simpler way to create basic visualizations`, + defaultMessage: + 'Create visualizations with our drag and drop editor. Switch between visualization types at any time.', + }), + note: i18n.translate('xpack.lens.visTypeAlias.note', { + defaultMessage: 'Recommended for most users.', }), icon: 'lensApp', stage: 'production', diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index ca6ca9b2722fd3..365bf8f4d6328a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -279,6 +279,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -334,6 +343,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -457,6 +475,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -512,6 +539,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -1019,6 +1055,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -1078,6 +1123,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = ` }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" @@ -1205,6 +1259,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-a" @@ -1264,6 +1327,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = }, ] } + displayValueSettings={ + Object { + "hideClippedValue": true, + "isAlternatingValueLabel": false, + "isValueContainedInElement": true, + "showValueLabel": false, + "valueFormatter": [Function], + } + } enableHistogramMode={false} groupId="left" id="d-b" diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap index b35f915336eeed..982f513ae10198 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap @@ -145,6 +145,9 @@ Object { "title": Array [ "", ], + "valueLabels": Array [ + "hide", + ], "xTitle": Array [ "", ], diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 6c9669dc239ea6..a5d292fdf265ad 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({ isVisible: false, position: Position.Top, }, + valueLabels: 'hide', axisTitlesVisibilitySettings: { type: 'lens_xy_axisTitlesVisibilityConfig', x: true, @@ -1867,6 +1868,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, @@ -1952,6 +1954,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, @@ -2023,6 +2026,7 @@ describe('xy_expression', () => { yTitle: '', yRightTitle: '', legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top }, + valueLabels: 'hide', tickLabelsVisibilitySettings: { type: 'lens_xy_tickLabelsConfig', x: true, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 877ddd3c0f27d3..d238e052a7c7fe 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -20,6 +20,10 @@ import { GeometryValue, XYChartSeriesIdentifier, StackMode, + RecursivePartial, + Theme, + VerticalAlignment, + HorizontalAlignment, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { @@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition< defaultMessage: 'Define how missing values are treated', }), }, + valueLabels: { + types: ['string'], + options: ['hide', 'inside'], + help: '', + }, tickLabelsVisibilitySettings: { types: ['lens_xy_tickLabelsConfig'], help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', { @@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: { }, }); +function mergeThemeWithValueLabelsStyling( + theme: RecursivePartial, + valuesLabelMode: string = 'hide', + isHorizontal: boolean +) { + const VALUE_LABELS_MAX_FONTSIZE = 15; + const VALUE_LABELS_MIN_FONTSIZE = 10; + const VALUE_LABELS_VERTICAL_OFFSET = -10; + const VALUE_LABELS_HORIZONTAL_OFFSET = 10; + + if (valuesLabelMode === 'hide') { + return theme; + } + return { + ...theme, + ...{ + barSeriesStyle: { + ...theme.barSeriesStyle, + displayValue: { + fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE }, + fill: { textInverted: true, textBorder: 2 }, + alignment: isHorizontal + ? { + vertical: VerticalAlignment.Middle, + } + : { horizontal: HorizontalAlignment.Center }, + offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0, + offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET, + }, + }, + }, + }; +} + function getIconForSeriesType(seriesType: SeriesType): IconType { return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty'; } @@ -254,7 +297,7 @@ export function XYChart({ onClickValue, onSelectRange, }: XYChartRenderProps) { - const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args; + const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args; const chartTheme = chartsThemeService.useChartsTheme(); const chartBaseTheme = chartsThemeService.useChartsBaseTheme(); @@ -396,6 +439,16 @@ export function XYChart({ return style; }; + const shouldShowValueLabels = + // No stacked bar charts + filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) && + // No histogram charts + !isHistogramViz; + + const baseThemeWithMaybeValueLabels = !shouldShowValueLabels + ? chartTheme + : mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate); + const colorAssignments = getColorAssignments(args.layers, data, formatFactory); return ( @@ -408,7 +461,7 @@ export function XYChart({ } legendPosition={legend.position} showLegendExtra={false} - theme={chartTheme} + theme={baseThemeWithMaybeValueLabels} baseTheme={chartBaseTheme} tooltip={{ headerFormatter: (d) => safeXAccessorLabelRenderer(d.value), @@ -613,6 +666,10 @@ export function XYChart({ }); } + const yAxis = yAxesConfiguration.find((axisConfiguration) => + axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor) + ); + const seriesProps: SeriesSpec = { splitSeriesAccessors: splitAccessor ? [splitAccessor] : [], stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [], @@ -649,9 +706,7 @@ export function XYChart({ palette.params ); }, - groupId: yAxesConfiguration.find((axisConfiguration) => - axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor) - )?.groupId, + groupId: yAxis?.groupId, enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor) && @@ -723,7 +778,19 @@ export function XYChart({ case 'bar_horizontal': case 'bar_horizontal_stacked': case 'bar_horizontal_percentage_stacked': - return ; + const valueLabelsSettings = { + displayValueSettings: { + // This format double fixes two issues in elastic-chart + // * when rotating the chart, the formatter is not correctly picked + // * in some scenarios value labels are not strings, and this breaks the elastic-chart lib + valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '', + showValueLabel: shouldShowValueLabels && valueLabels !== 'hide', + isAlternatingValueLabel: false, + isValueContainedInElement: true, + hideClippedValue: true, + }, + }; + return ; case 'area_stacked': case 'area_percentage_stacked': return ( diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index 41d18e5199e4c2..bf4ffaa36a8700 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -5,7 +5,8 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types'; +import { FramePublicAPI } from '../types'; +import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types'; export function isHorizontalSeries(seriesType: SeriesType) { return ( @@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => { layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null ); }; + +export function hasHistogramSeries( + layers: ValidLayer[] = [], + datasourceLayers?: FramePublicAPI['datasourceLayers'] +) { + if (!datasourceLayers) { + return false; + } + const validLayers = layers.filter(({ accessors }) => accessors.length); + + return validLayers.some(({ layerId, xAccessor }: ValidLayer) => { + const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor); + return ( + xAxisOperation && + xAxisOperation.isBucketed && + xAxisOperation.scale && + xAxisOperation.scale !== 'ordinal' + ); + }); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 6148824bfec216..05a4b7f460adb9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -43,6 +43,7 @@ describe('#toExpression', () => { xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'Carry', tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true }, @@ -67,6 +68,7 @@ describe('#toExpression', () => { (xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -87,6 +89,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -113,6 +116,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -136,6 +140,7 @@ describe('#toExpression', () => { xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -156,6 +161,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -191,6 +197,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -217,6 +224,7 @@ describe('#toExpression', () => { const expression = xyVisualization.toExpression( { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -238,4 +246,25 @@ describe('#toExpression', () => { yRight: [true], }); }); + + it('should correctly report the valueLabels visibility settings', () => { + const expression = xyVisualization.toExpression( + { + legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'inside', + preferredSeriesType: 'bar', + layers: [ + { + layerId: 'first', + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + }, + ], + }, + frame.datasourceLayers + ) as Ast; + expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside'); + }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 904d1541a85ec6..df773146cde4db 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -7,13 +7,9 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { PaletteRegistry } from 'src/plugins/charts/public'; -import { State, LayerConfig } from './types'; +import { State, ValidLayer, LayerConfig } from './types'; import { OperationMetadata, DatasourcePublicAPI } from '../types'; -interface ValidLayer extends LayerConfig { - xAccessor: NonNullable; -} - export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => { const originalOrder = datasource .getTableSpec() @@ -60,6 +56,7 @@ export function toPreviewExpression( ...state.legend, isVisible: false, }, + valueLabels: 'hide', }, datasourceLayers, paletteService, @@ -197,6 +194,7 @@ export const buildExpression = ( ], }, ], + valueLabels: [state?.valueLabels || 'hide'], layers: validLayers.map((layer) => { const columnToLabel: Record = {}; diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index d1e78aec57998d..d21ac675d0745a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -364,6 +364,8 @@ export type SeriesType = export type YAxisMode = 'auto' | 'left' | 'right'; +export type ValueLabelConfig = 'hide' | 'inside' | 'outside'; + export interface YConfig { forAccessor: string; axisMode?: YAxisMode; @@ -381,6 +383,10 @@ export interface LayerConfig { palette?: PaletteOutput; } +export interface ValidLayer extends LayerConfig { + xAccessor: NonNullable; +} + export type LayerArgs = LayerConfig & { columnToLabel?: string; // Actually a JSON key-value pair yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; @@ -398,6 +404,7 @@ export interface XYArgs { yTitle: string; yRightTitle: string; legend: LegendConfig & { type: 'lens_xy_legendConfig' }; + valueLabels: ValueLabelConfig; layers: LayerArgs[]; fittingFunction?: FittingFunction; axisTitlesVisibilitySettings?: AxesSettingsConfig & { @@ -411,6 +418,7 @@ export interface XYArgs { export interface XYState { preferredSeriesType: SeriesType; legend: LegendConfig; + valueLabels?: ValueLabelConfig; fittingFunction?: FittingFunction; layers: LayerConfig[]; xTitle?: string; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 7c49afa53af3ea..5127e5c2c2597a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -15,6 +15,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks' function exampleState(): State { return { legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -150,6 +151,7 @@ describe('xy_visualization', () => { }, "preferredSeriesType": "bar_stacked", "title": "Empty XY chart", + "valueLabels": "hide", } `); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index c7f775586ca0da..7e155de14a39ab 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -145,6 +145,7 @@ export const getXyVisualization = ({ state || { title: 'Empty XY chart', legend: { isVisible: true, position: Position.Right }, + valueLabels: 'hide', preferredSeriesType: defaultSeriesType, layers: [ { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss index 5b14fca78e65d8..b9ff6a56d8e35e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss @@ -1,3 +1,3 @@ .lnsXyToolbar__popover { width: 320px; -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 2114d63fcfacd4..721bff8684a195 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -21,6 +21,7 @@ describe('XY Config panels', () => { function testState(): State { return { legend: { isVisible: true, position: Position.Right }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -115,8 +116,9 @@ describe('XY Config panels', () => { expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry'); }); - it('should disable the popover if there is no area or line series', () => { + it('should show currently selected value labels display setting', () => { const state = testState(); + const component = shallow( { ...state, layers: [{ ...state.layers[0], seriesType: 'bar' }], fittingFunction: 'Carry', + valueLabels: 'inside', + }} + /> + ); + + expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside'); + }); + + it('should disable the popover for stacked bar charts', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should disable the popover for percentage area charts', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should disabled the popover if there is histogram series', () => { + // make it detect an histogram series + frame.datasourceLayers.first.getOperationForColumnId = jest.fn().mockReturnValueOnce({ + isBucketed: true, + scale: 'interval', + }); + const state = testState(); + const component = shallow( + + ); + + expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true); + }); + + it('should show the popover and display field enabled for bar and horizontal_bar series', () => { + const state = testState(); + + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true); + }); + + it('should hide the fitting option for bar series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false); + }); + + it('should hide in the popover the display option for area and line series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false); + }); + + it('should keep the display option for bar series with multiple layers', () => { + frame.datasourceLayers = { + ...frame.datasourceLayers, + second: createMockDatasource('test').publicAPIMock, + }; + + const state = testState(); + const component = shallow( + ); - expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true); + expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true); }); it('should disable the popover if there is no right axis', () => { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index 97e42113fc180e..a22530c5743b44 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -27,8 +27,20 @@ import { VisualizationToolbarProps, VisualizationDimensionEditorProps, } from '../types'; -import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types'; -import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers'; +import { + State, + SeriesType, + visualizationTypes, + YAxisMode, + AxesSettingsConfig, + ValidLayer, +} from './types'; +import { + isHorizontalChart, + isHorizontalSeries, + getSeriesColor, + hasHistogramSeries, +} from './state_helpers'; import { trackUiEvent } from '../lens_ui_telemetry'; import { fittingFunctionDefinitions } from './fitting_functions'; import { ToolbarPopover, LegendSettingsPopover } from '../shared_components'; @@ -74,6 +86,27 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: }, ]; +const valueLabelsOptions: Array<{ + id: string; + value: 'hide' | 'inside' | 'outside'; + label: string; +}> = [ + { + id: `value_labels_hide`, + value: 'hide', + label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.auto', { + defaultMessage: 'Hide', + }), + }, + { + id: `value_labels_inside`, + value: 'inside', + label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.inside', { + defaultMessage: 'Show', + }), + }, +]; + export function LayerContextMenu(props: VisualizationLayerWidgetProps) { const { state, layerId } = props; const horizontalOnly = isHorizontalChart(state.layers); @@ -118,12 +151,24 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) { } export function XyToolbar(props: VisualizationToolbarProps) { - const { state, setState } = props; + const { state, setState, frame } = props; const hasNonBarSeries = state?.layers.some(({ seriesType }) => ['area_stacked', 'area', 'line'].includes(seriesType) ); + const hasBarNotStacked = state?.layers.some(({ seriesType }) => + ['bar', 'bar_horizontal'].includes(seriesType) + ); + + const isAreaPercentage = state?.layers.some( + ({ seriesType }) => seriesType === 'area_percentage_stacked' + ); + + const isHistogramSeries = Boolean( + hasHistogramSeries(state?.layers as ValidLayer[], frame.datasourceLayers) + ); + const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false; const axisGroups = getAxesConfiguration(state?.layers, shouldRotate); @@ -191,54 +236,99 @@ export function XyToolbar(props: VisualizationToolbarProps) { : !state?.legend.isVisible ? 'hide' : 'show'; + + const valueLabelsVisibilityMode = state?.valueLabels || 'hide'; + + const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries; + const isFittingEnabled = hasNonBarSeries; + return ( - - { - return { - value: id, - dropdownDisplay: ( - <> - {title} - -

{description}

-
- - ), - inputDisplay: title, - }; + {isValueLabelsEnabled ? ( + + {i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', { + defaultMessage: 'Labels', + })} + + } + > + value === valueLabelsVisibilityMode)! + .id + } + onChange={(modeId) => { + const newMode = valueLabelsOptions.find(({ id }) => id === modeId)!.value; + setState({ ...state, valueLabels: newMode }); + }} + /> + + ) : null} + {isFittingEnabled ? ( + setState({ ...state, fittingFunction: value })} - itemLayoutAlign="top" - hasDividers - /> - + > + { + return { + value: id, + dropdownDisplay: ( + <> + {title} + +

{description}

+
+ + ), + inputDisplay: title, + }; + })} + valueOfSelected={state?.fittingFunction || 'None'} + onChange={(value) => setState({ ...state, fittingFunction: value })} + itemLayoutAlign="top" + hasDividers + /> +
+ ) : null}
{ keptLayerIds: [], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -249,6 +250,7 @@ describe('xy_suggestions', () => { keptLayerIds: ['first'], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -289,6 +291,7 @@ describe('xy_suggestions', () => { keptLayerIds: ['first', 'second'], state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -523,6 +526,7 @@ describe('xy_suggestions', () => { }, state: { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', layers: [ { @@ -575,6 +579,7 @@ describe('xy_suggestions', () => { test('keeps existing seriesType for initial tables', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', preferredSeriesType: 'line', layers: [ @@ -608,6 +613,7 @@ describe('xy_suggestions', () => { test('makes a visible seriesType suggestion for unchanged table without split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -648,6 +654,7 @@ describe('xy_suggestions', () => { test('suggests seriesType and stacking when there is a split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -693,6 +700,7 @@ describe('xy_suggestions', () => { (generateId as jest.Mock).mockReturnValueOnce('dummyCol'); const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', fittingFunction: 'None', preferredSeriesType: 'bar', layers: [ @@ -725,6 +733,7 @@ describe('xy_suggestions', () => { test('suggests stacking for unchanged table that has a split', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', layers: [ @@ -760,6 +769,7 @@ describe('xy_suggestions', () => { test('keeps column to dimension mappings on extended tables', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -802,6 +812,7 @@ describe('xy_suggestions', () => { test('changes column mappings when suggestion is reorder', () => { const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, @@ -845,6 +856,7 @@ describe('xy_suggestions', () => { (generateId as jest.Mock).mockReturnValueOnce('dummyCol'); const currentState: XYState = { legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', preferredSeriesType: 'bar', fittingFunction: 'None', axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index edb7c4ed522433..7bbb0395773062 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -509,6 +509,7 @@ function buildSuggestion({ const state: State = { legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right }, + valueLabels: currentState?.valueLabels || 'hide', fittingFunction: currentState?.fittingFunction || 'None', xTitle: currentState?.xTitle, yTitle: currentState?.yTitle, diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 6f3a5b61ddc6c5..f52feb3552abb2 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -22,5 +22,5 @@ "ui": true, "server": true, "extraPublicDirs": ["common/constants"], - "requiredBundles": ["kibanaReact", "kibanaUtils", "home"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss"] } diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js index 96dad0c01139e4..dc3ace69e5a615 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js @@ -11,7 +11,7 @@ import { isRetina } from '../../../meta'; import { addSpriteSheetToMapFromImageData, loadSpriteSheetImageData, -} from '../../../connected_components/map/mb/utils'; //todo move this implementation +} from '../../../connected_components/mb_map/utils'; //todo move this implementation const MB_STYLE_TYPE_TO_OPACITY = { fill: ['fill-opacity'], diff --git a/x-pack/plugins/maps/public/connected_components/_index.scss b/x-pack/plugins/maps/public/connected_components/_index.scss index a952b3b5459227..19c11d3fde6624 100644 --- a/x-pack/plugins/maps/public/connected_components/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/_index.scss @@ -2,4 +2,4 @@ @import 'layer_panel/index'; @import 'widget_overlay/index'; @import 'toolbar_overlay/index'; -@import 'map/features_tooltip/index'; +@import 'mb_map/features_tooltip/index'; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 352aed4a8cc935..169875e63a5361 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -13,7 +13,7 @@ import uuid from 'uuid/v4'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; // @ts-expect-error -import { MBMap } from '../map/mb'; +import { MBMap } from '../mb_map'; // @ts-expect-error import { WidgetOverlay } from '../widget_overlay'; // @ts-expect-error diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_circle.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_circle.ts diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js index 0356a8267c18af..089d4be28dff7e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.js @@ -6,7 +6,7 @@ import _ from 'lodash'; import React from 'react'; -import { DRAW_TYPE } from '../../../../../common/constants'; +import { DRAW_TYPE } from '../../../../common/constants'; import MapboxDraw from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw-unminified'; import DrawRectangle from 'mapbox-gl-draw-rectangle-mode'; import { DrawCircle } from './draw_circle'; @@ -15,7 +15,7 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../../common/elasticsearch_util'; +} from '../../../../common/elasticsearch_util'; import { DrawTooltip } from './draw_tooltip'; const DRAW_RECTANGLE = 'draw_rectangle'; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js index c8bde29b94fb68..dd93b038ff8a11 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_tooltip.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_tooltip.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DRAW_TYPE } from '../../../../../common/constants'; +import { DRAW_TYPE } from '../../../../common/constants'; const noop = () => {}; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js similarity index 83% rename from x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js index bc026c41fcf0a4..230ad5b3f39d59 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.js @@ -6,8 +6,8 @@ import { connect } from 'react-redux'; import { DrawControl } from './draw_control'; -import { updateDrawState } from '../../../../actions'; -import { getDrawState, isDrawingFilter } from '../../../../selectors/map_selectors'; +import { updateDrawState } from '../../../actions'; +import { getDrawState, isDrawingFilter } from '../../../selectors/map_selectors'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/feature_properties.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/feature_properties.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/__snapshots__/tooltip_header.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/__snapshots__/tooltip_header.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/_index.scss rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/_index.scss diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/feature_properties.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_properties.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/features_tooltip.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/features_tooltip.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.js diff --git a/x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/features_tooltip/tooltip_header.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/tooltip_header.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts similarity index 87% rename from x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts index 20fb8186f9870f..853819eb289a3e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/get_initial_view.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/get_initial_view.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { INITIAL_LOCATION } from '../../../../common/constants'; -import { Goto, MapCenterAndZoom } from '../../../../common/descriptor_types'; -import { MapSettings } from '../../../reducers/map'; +import { INITIAL_LOCATION } from '../../../common/constants'; +import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types'; +import { MapSettings } from '../../reducers/map'; export async function getInitialView( goto: Goto | null, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/image_utils.js rename to x-pack/plugins/maps/public/connected_components/mb_map/image_utils.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/index.js similarity index 84% rename from x-pack/plugins/maps/public/connected_components/map/mb/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/index.js index 4b8df07bd1f391..cccd5e571d3e84 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/index.js @@ -5,7 +5,7 @@ */ import { connect } from 'react-redux'; -import { MBMap } from './view'; +import { MBMap } from './mb_map'; import { mapExtentChanged, mapReady, @@ -14,7 +14,7 @@ import { clearMouseCoordinates, clearGoto, setMapInitError, -} from '../../../actions'; +} from '../../actions'; import { getLayerList, getMapReady, @@ -25,9 +25,9 @@ import { isViewControlHidden, getSpatialFiltersLayer, getMapSettings, -} from '../../../selectors/map_selectors'; +} from '../../selectors/map_selectors'; -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; +import { getInspectorAdapters } from '../../reducers/non_serializable_instances'; function mapStateToProps(state = {}) { return { @@ -72,7 +72,5 @@ function mapDispatchToProps(dispatch) { }; } -const connectedMBMap = connect(mapStateToProps, mapDispatchToProps, null, { - forwardRef: true, -})(MBMap); -export { connectedMBMap as MBMap }; +const connected = connect(mapStateToProps, mapDispatchToProps)(MBMap); +export { connected as MBMap }; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js index e2050724ef6845..a28cc75f6d89d1 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb.utils.test.js @@ -5,7 +5,7 @@ */ import { removeOrphanedSourcesAndLayers } from './utils'; -import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants'; +import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants'; import _ from 'lodash'; class MockMbMap { diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/view.js rename to x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js index ddc48cfc9c329b..04c376a093623b 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.js @@ -6,15 +6,15 @@ import _ from 'lodash'; import React from 'react'; -import { ResizeChecker } from '../../../../../../../src/plugins/kibana_utils/public'; +import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils'; import { syncLayerOrder } from './sort_layers'; -import { getGlyphUrl, isRetina } from '../../../meta'; +import { getGlyphUrl, isRetina } from '../../meta'; import { DECIMAL_DEGREES_PRECISION, KBN_TOO_MANY_FEATURES_IMAGE_ID, ZOOM_PRECISION, -} from '../../../../common/constants'; +} from '../../../common/constants'; import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; @@ -23,9 +23,9 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; -import { clampToLatBounds, clampToLonBounds } from '../../../../common/elasticsearch_util'; +import { clampToLatBounds, clampToLonBounds } from '../../../common/elasticsearch_util'; import { getInitialView } from './get_initial_view'; -import { getPreserveDrawingBuffer } from '../../../kibana_services'; +import { getPreserveDrawingBuffer } from '../../kibana_services'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts index e26a1e43509c81..9e85c7b04b2662 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts @@ -8,8 +8,8 @@ import _ from 'lodash'; import { Map as MbMap, Layer as MbLayer, Style as MbStyle } from 'mapbox-gl'; import { getIsTextLayer, syncLayerOrder } from './sort_layers'; -import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants'; -import { ILayer } from '../../../classes/layers/layer'; +import { SPATIAL_FILTERS_LAYER_ID } from '../../../common/constants'; +import { ILayer } from '../../classes/layers/layer'; let moveCounter = 0; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts rename to x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts index 0c970fe6635576..dda43269e32d83 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/sort_layers.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts @@ -5,7 +5,7 @@ */ import { Map as MbMap, Layer as MbLayer } from 'mapbox-gl'; -import { ILayer } from '../../../classes/layers/layer'; +import { ILayer } from '../../classes/layers/layer'; // "Layer" is overloaded and can mean the following // 1) Map layer (ILayer): A single map layer consists of one to many mapbox layers. diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_control.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_control.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/__snapshots__/tooltip_popover.test.js.snap rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/__snapshots__/tooltip_popover.test.js.snap diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js similarity index 94% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js index 407dcf1997aeb2..7d2f2b05d6f11d 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/index.js @@ -11,13 +11,13 @@ import { openOnClickTooltip, closeOnHoverTooltip, openOnHoverTooltip, -} from '../../../../actions'; +} from '../../../actions'; import { getLayerList, getOpenTooltips, getHasLockedTooltips, isDrawingFilter, -} from '../../../../selectors/map_selectors'; +} from '../../../selectors/map_selectors'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js index edfeb3c76b1046..b178eef6fa5d39 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.js @@ -6,9 +6,9 @@ import _ from 'lodash'; import React from 'react'; -import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../../common/constants'; +import { FEATURE_ID_PROPERTY_NAME, LON_INDEX } from '../../../../common/constants'; import { TooltipPopover } from './tooltip_popover'; -import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../../classes/util/mb_filter_expressions'; +import { EXCLUDE_TOO_MANY_FEATURES_BOX } from '../../../classes/util/mb_filter_expressions'; function justifyAnchorLocation(mbLngLat, targetFeature) { let popupAnchorLocation = [mbLngLat.lng, mbLngLat.lat]; // default popup location to mouse location diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_control.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.js diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js similarity index 97% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js index 4cfddf00340392..ca4864f79940ee 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.js @@ -5,8 +5,8 @@ */ import React, { Component } from 'react'; -import { LAT_INDEX, LON_INDEX } from '../../../../../common/constants'; -import { FeaturesTooltip } from '../../features_tooltip/features_tooltip'; +import { LAT_INDEX, LON_INDEX } from '../../../../common/constants'; +import { FeaturesTooltip } from '../features_tooltip/features_tooltip'; import { EuiPopover, EuiText } from '@elastic/eui'; const noop = () => {}; diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js similarity index 98% rename from x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js rename to x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js index 205ca7337277d5..b15c3fce6c0b7e 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/tooltip_control/tooltip_popover.test.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../features_tooltip/features_tooltip', () => ({ +jest.mock('../features_tooltip/features_tooltip', () => ({ FeaturesTooltip: () => { return
mockFeaturesTooltip
; }, diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js similarity index 100% rename from x-pack/plugins/maps/public/connected_components/map/mb/utils.js rename to x-pack/plugins/maps/public/connected_components/mb_map/utils.js diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js index b7e95cdf987db0..a2e76216c7def2 100644 --- a/x-pack/plugins/maps/public/maps_vis_type_alias.js +++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js @@ -16,14 +16,6 @@ export function getMapsVisTypeAlias(visualizations, showMapVisualizationTypes) { defaultMessage: 'Create and style maps with multiple layers and indices.', }); - const legacyMapVisualizationWarning = i18n.translate( - 'xpack.maps.visTypeAlias.legacyMapVizWarning', - { - defaultMessage: `Use the Maps app instead of Coordinate Map and Region Map. -The Maps app offers more functionality and is easier to use.`, - } - ); - return { aliasApp: APP_ID, aliasPath: `/${MAP_PATH}`, @@ -31,9 +23,7 @@ The Maps app offers more functionality and is easier to use.`, title: i18n.translate('xpack.maps.visTypeAlias.title', { defaultMessage: 'Maps', }), - description: showMapVisualizationTypes - ? `${description} ${legacyMapVisualizationWarning}` - : description, + description: description, icon: APP_ICON, stage: 'production', }; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 0b797c7b8ef608..75a3f8ef5ede84 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -28,8 +28,12 @@ import { featureCatalogueEntry } from './feature_catalogue_entry'; // @ts-ignore import { getMapsVisTypeAlias } from './maps_vis_type_alias'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; -import { VisualizationsSetup } from '../../../../src/plugins/visualizations/public'; +import { + VisualizationsSetup, + VisualizationsStart, +} from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; +import { PLUGIN_ID_OSS } from '../../../../src/plugins/maps_oss/common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { createMapsUrlGenerator, @@ -72,6 +76,7 @@ export interface MapsPluginStartDependencies { navigation: NavigationPublicPluginStart; uiActions: UiActionsStart; share: SharePluginStart; + visualizations: VisualizationsStart; savedObjects: SavedObjectsStart; } @@ -145,6 +150,8 @@ export class MapsPlugin setLicensingPluginStart(plugins.licensing); plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction); setStartServices(core, plugins); + // unregisters the OSS alias + plugins.visualizations.unRegisterAlias(PLUGIN_ID_OSS); return { createSecurityLayerDescriptors, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index 785f3ac9cd4dcf..d46adff3de2a32 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -33,6 +33,7 @@ export const MAX_COLUMNS = 10; export const DEFAULT_REGRESSION_COLUMNS = 8; export const BASIC_NUMERICAL_TYPES = new Set([ + ES_FIELD_TYPES.UNSIGNED_LONG, ES_FIELD_TYPES.LONG, ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.SHORT, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index c3b1de64c3eb5e..fec60f221b4fca 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -27,6 +27,7 @@ const supportedTypes: string[] = [ ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.FLOAT, ES_FIELD_TYPES.LONG, + ES_FIELD_TYPES.UNSIGNED_LONG, ES_FIELD_TYPES.BYTE, ES_FIELD_TYPES.HALF_FLOAT, ES_FIELD_TYPES.SCALED_FLOAT, @@ -245,6 +246,7 @@ function getNumericalFields(fields: Field[]): Field[] { return fields.filter( (f) => f.type === ES_FIELD_TYPES.LONG || + f.type === ES_FIELD_TYPES.UNSIGNED_LONG || f.type === ES_FIELD_TYPES.INTEGER || f.type === ES_FIELD_TYPES.SHORT || f.type === ES_FIELD_TYPES.BYTE || diff --git a/x-pack/plugins/observability/common/annotations.ts b/x-pack/plugins/observability/common/annotations.ts index 6aea4d3d92f9b4..f7ab243cf73f3c 100644 --- a/x-pack/plugins/observability/common/annotations.ts +++ b/x-pack/plugins/observability/common/annotations.ts @@ -5,7 +5,24 @@ */ import * as t from 'io-ts'; -import { dateAsStringRt } from '../../apm/common/runtime_types/date_as_string_rt'; +import { either } from 'fp-ts/lib/Either'; + +/** + * Checks whether a string is a valid ISO timestamp, + * but doesn't convert it into a Date object when decoding. + * + * Copied from x-pack/plugins/apm/common/runtime_types/date_as_string_rt.ts. + */ +const dateAsStringRt = new t.Type( + 'DateAsString', + t.string.is, + (input, context) => + either.chain(t.string.validate(input, context), (str) => { + const date = new Date(str); + return isNaN(date.getTime()) ? t.failure(input, context) : t.success(str); + }), + t.identity +); export const createAnnotationRt = t.intersection([ t.type({ diff --git a/x-pack/plugins/security/common/model/authenticated_user.mock.ts b/x-pack/plugins/security/common/model/authenticated_user.mock.ts index 0393c94da8d406..d9947bc96b78b8 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.mock.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.mock.ts @@ -15,7 +15,7 @@ export function mockAuthenticatedUser(user: Partial = {}) { enabled: true, authentication_realm: { name: 'native1', type: 'native' }, lookup_realm: { name: 'native1', type: 'native' }, - authentication_provider: 'basic1', + authentication_provider: { type: 'basic', name: 'basic1' }, authentication_type: 'realm', ...user, }; diff --git a/x-pack/plugins/security/common/model/authenticated_user.test.ts b/x-pack/plugins/security/common/model/authenticated_user.test.ts index cdf1423df56df8..d253fed97f353e 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.test.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.test.ts @@ -12,6 +12,7 @@ describe('#canUserChangePassword', () => { expect( canUserChangePassword({ username: 'foo', + authentication_provider: { type: 'basic', name: 'basic1' }, authentication_realm: { name: 'the realm name', type: realm, @@ -25,6 +26,7 @@ describe('#canUserChangePassword', () => { expect( canUserChangePassword({ username: 'foo', + authentication_provider: { type: 'the provider type', name: 'does not matter' }, authentication_realm: { name: 'the realm name', type: 'does not matter', diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts index 5ea420af202dc2..d5c8d4e474c601 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import type { AuthenticationProvider } from '../types'; import { User } from './user'; const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; @@ -28,9 +29,9 @@ export interface AuthenticatedUser extends User { lookup_realm: UserRealm; /** - * Name of the Kibana authentication provider that used to authenticate user. + * The authentication provider that used to authenticate user. */ - authentication_provider: string; + authentication_provider: AuthenticationProvider; /** * The AuthenticationType used by ES to authenticate the user. diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 80aeb4f8b29598..eef45598d1761c 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -10,14 +10,14 @@ import { ILegacyClusterClient, IBasePath, } from '../../../../../src/core/server'; -import { SecurityLicense } from '../../common/licensing'; -import { AuthenticatedUser } from '../../common/model'; -import { AuthenticationProvider } from '../../common/types'; +import type { SecurityLicense } from '../../common/licensing'; +import type { AuthenticatedUser } from '../../common/model'; +import type { AuthenticationProvider } from '../../common/types'; import { SecurityAuditLogger, AuditServiceSetup, userLoginEvent } from '../audit'; -import { ConfigType } from '../config'; +import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; -import { SecurityFeatureUsageServiceStart } from '../feature_usage'; -import { SessionValue, Session } from '../session_management'; +import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; +import type { SessionValue, Session } from '../session_management'; import { AuthenticationProviderOptions, @@ -259,7 +259,7 @@ export class Authenticator { isLoginAttemptWithProviderName(attempt) && this.providers.has(attempt.provider.name) ? [[attempt.provider.name, this.providers.get(attempt.provider.name)!]] : isLoginAttemptWithProviderType(attempt) - ? [...this.providerIterator(existingSessionValue)].filter( + ? [...this.providerIterator(existingSessionValue?.provider.name)].filter( ([, { type }]) => type === attempt.provider.type ) : []; @@ -338,7 +338,9 @@ export class Authenticator { ); } - for (const [providerName, provider] of this.providerIterator(existingSessionValue)) { + for (const [providerName, provider] of this.providerIterator( + existingSessionValue?.provider.name + )) { // Check if current session has been set by this provider. const ownsSession = existingSessionValue?.provider.name === providerName && @@ -395,7 +397,7 @@ export class Authenticator { // active session already some providers can still properly respond to the 3rd-party logout // request. For example SAML provider can process logout request encoded in `SAMLRequest` // query string parameter. - for (const [, provider] of this.providerIterator(null)) { + for (const [, provider] of this.providerIterator()) { const deauthenticationResult = await provider.logout(request); if (!deauthenticationResult.notHandled()) { return deauthenticationResult; @@ -473,22 +475,22 @@ export class Authenticator { } /** - * Returns provider iterator where providers are sorted in the order of priority (based on the session ownership). - * @param sessionValue Current session value. + * Returns provider iterator starting from the suggested provider if any. + * @param suggestedProviderName Optional name of the provider to return first. */ private *providerIterator( - sessionValue: SessionValue | null + suggestedProviderName?: string | null ): IterableIterator<[string, BaseAuthenticationProvider]> { - // If there is no session to predict which provider to use first, let's use the order - // providers are configured in. Otherwise return provider that owns session first, and only then the rest + // If there is no provider suggested or suggested provider isn't configured, let's use the order + // providers are configured in. Otherwise return suggested provider first, and only then the rest // of providers. - if (!sessionValue) { + if (!suggestedProviderName || !this.providers.has(suggestedProviderName)) { yield* this.providers; } else { - yield [sessionValue.provider.name, this.providers.get(sessionValue.provider.name)!]; + yield [suggestedProviderName, this.providers.get(suggestedProviderName)!]; for (const [providerName, provider] of this.providers) { - if (providerName !== sessionValue.provider.name) { + if (providerName !== suggestedProviderName) { yield [providerName, provider]; } } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index 6b13c32a2f1ed7..030b2a6e968afb 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -113,7 +113,7 @@ export abstract class BaseAuthenticationProvider { ...(await this.options.client .asScoped({ headers: { ...request.headers, ...authHeaders } }) .callAsCurrentUser('shield.authenticate')), - authentication_provider: this.options.name, + authentication_provider: { type: this.type, name: this.options.name }, } as AuthenticatedUser); } } diff --git a/x-pack/plugins/security/server/authentication/providers/http.test.ts b/x-pack/plugins/security/server/authentication/providers/http.test.ts index c221ecd3f1e20f..512a8ead2c32b0 100644 --- a/x-pack/plugins/security/server/authentication/providers/http.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/http.test.ts @@ -136,7 +136,10 @@ describe('HTTPAuthenticationProvider', () => { }); await expect(provider.authenticate(request)).resolves.toEqual( - AuthenticationResult.succeeded({ ...user, authentication_provider: 'http' }) + AuthenticationResult.succeeded({ + ...user, + authentication_provider: { type: 'http', name: 'http' }, + }) ); expectAuthenticateCall(mockOptions.client, { headers: { authorization: header } }); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index ee89af10cbebaf..4ea395e7b53de4 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -128,7 +128,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer some-token' }, state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' }, @@ -164,7 +164,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer some-token' }, authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' }, @@ -361,7 +361,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization } } ) ); @@ -401,7 +401,7 @@ describe('KerberosAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'kerberos' }, + { ...user, authentication_provider: { type: 'kerberos', name: 'kerberos' } }, { authHeaders: { authorization: 'Bearer newfoo' }, state: { accessToken: 'newfoo', refreshToken: 'newbar' }, diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index fe46b159833bc8..dfea7e508b3333 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -29,7 +29,7 @@ describe('OIDCAuthenticationProvider', () => { mockOptions = mockAuthenticationProviderOptions({ name: 'oidc' }); mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockUser = mockAuthenticatedUser({ authentication_provider: 'oidc' }); + mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'oidc', name: 'oidc' } }); mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { if (method === 'shield.authenticate') { return mockUser; diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 7e76f4c81998d8..969682b225ceb4 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -127,7 +127,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -169,7 +169,7 @@ describe('PKIAuthenticationProvider', () => { await expect(operation(request)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -356,7 +356,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -405,7 +405,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: 'Bearer access-token' }, state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }, @@ -486,7 +486,7 @@ describe('PKIAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'pki' }, + { ...user, authentication_provider: { type: 'pki', name: 'pki' } }, { authHeaders: { authorization: `Bearer ${state.accessToken}` } } ) ); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index d160b79bf659d5..a1f2e99c133577 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -28,7 +28,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions = mockAuthenticationProviderOptions({ name: 'saml' }); mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockUser = mockAuthenticatedUser({ authentication_provider: 'saml' }); + mockUser = mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }); mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method) => { if (method === 'shield.authenticate') { return mockUser; @@ -490,7 +490,9 @@ describe('SAMLAuthenticationProvider', () => { for (const [description, response] of [ [ 'current session is valid', - Promise.resolve(mockAuthenticatedUser({ authentication_provider: 'saml' })), + Promise.resolve( + mockAuthenticatedUser({ authentication_provider: { type: 'saml', name: 'saml' } }) + ), ], [ 'current session is is expired', diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index 8cd11bce982691..4501004ab69c17 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -60,7 +60,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.login(request, credentials)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization }, state: tokenPair } ) ); @@ -196,7 +196,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization } } ) ); @@ -236,7 +236,7 @@ describe('TokenAuthenticationProvider', () => { await expect(provider.authenticate(request, tokenPair)).resolves.toEqual( AuthenticationResult.succeeded( - { ...user, authentication_provider: 'token' }, + { ...user, authentication_provider: { type: 'token', name: 'token' } }, { authHeaders: { authorization: 'Bearer newfoo' }, state: { accessToken: 'newfoo', refreshToken: 'newbar' }, diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 32b8708d2b3814..e75c0d1c4085fe 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -36,6 +36,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -54,8 +58,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -82,6 +84,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -100,8 +106,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -128,6 +132,10 @@ describe('config schema', () => { "hint": undefined, "icon": undefined, "order": 0, + "session": Object { + "idleTimeout": undefined, + "lifespan": undefined, + }, "showInSelector": true, }, }, @@ -145,8 +153,6 @@ describe('config schema', () => { "secureCookies": false, "session": Object { "cleanupInterval": "PT1H", - "idleTimeout": null, - "lifespan": null, }, } `); @@ -387,6 +393,35 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "basic": Object { + "basic1": Object { + "description": "Log in with Elasticsearch", + "enabled": true, + "icon": "logoElasticsearch", + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -439,6 +474,35 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + token: { token1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "token": Object { + "token1": Object { + "description": "Log in with Elasticsearch", + "enabled": true, + "icon": "logoElasticsearch", + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -477,6 +541,33 @@ describe('config schema', () => { "pki1": Object { "enabled": true, "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + pki: { pki1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "pki": Object { + "pki1": Object { + "enabled": true, + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -517,6 +608,33 @@ describe('config schema', () => { "kerberos1": Object { "enabled": true, "order": 0, + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + kerberos: { kerberos1: { order: 0, session: { idleTimeout: 123, lifespan: 546 } } }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "kerberos": Object { + "kerberos1": Object { + "enabled": true, + "order": 0, + "session": Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -562,12 +680,53 @@ describe('config schema', () => { "enabled": true, "order": 0, "realm": "oidc1", + "session": Object {}, "showInSelector": true, }, "oidc2": Object { "enabled": true, "order": 1, "realm": "oidc2", + "session": Object {}, + "showInSelector": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + oidc: { + oidc1: { order: 0, realm: 'oidc1', session: { idleTimeout: 123 } }, + oidc2: { order: 1, realm: 'oidc2', session: { idleTimeout: 321, lifespan: 546 } }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "oidc": Object { + "oidc1": Object { + "enabled": true, + "order": 0, + "realm": "oidc1", + "session": Object { + "idleTimeout": "PT0.123S", + }, + "showInSelector": true, + }, + "oidc2": Object { + "enabled": true, + "order": 1, + "realm": "oidc2", + "session": Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.546S", + }, "showInSelector": true, }, }, @@ -617,6 +776,62 @@ describe('config schema', () => { "enabled": true, "order": 0, "realm": "saml1", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": false, + }, + "saml2": Object { + "enabled": true, + "maxRedirectURLSize": ByteSizeValue { + "valueInBytes": 1024, + }, + "order": 1, + "realm": "saml2", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": false, + }, + "saml3": Object { + "enabled": true, + "order": 2, + "realm": "saml3", + "session": Object {}, + "showInSelector": true, + "useRelayStateDeepLink": true, + }, + }, + } + `); + }); + + it('can be successfully validated with session config overrides', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + saml: { + saml1: { order: 0, realm: 'saml1', session: { idleTimeout: 123 } }, + saml2: { + order: 1, + realm: 'saml2', + maxRedirectURLSize: '1kb', + session: { idleTimeout: 321, lifespan: 546 }, + }, + saml3: { order: 2, realm: 'saml3', useRelayStateDeepLink: true }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "saml": Object { + "saml1": Object { + "enabled": true, + "order": 0, + "realm": "saml1", + "session": Object { + "idleTimeout": "PT0.123S", + }, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -627,6 +842,10 @@ describe('config schema', () => { }, "order": 1, "realm": "saml2", + "session": Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.546S", + }, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -634,6 +853,7 @@ describe('config schema', () => { "enabled": true, "order": 2, "realm": "saml3", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": true, }, @@ -701,6 +921,7 @@ describe('config schema', () => { "enabled": true, "icon": "logoElasticsearch", "order": 0, + "session": Object {}, "showInSelector": true, }, "basic2": Object { @@ -708,6 +929,7 @@ describe('config schema', () => { "enabled": false, "icon": "logoElasticsearch", "order": 1, + "session": Object {}, "showInSelector": true, }, }, @@ -716,6 +938,7 @@ describe('config schema', () => { "enabled": false, "order": 3, "realm": "saml3", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -723,6 +946,7 @@ describe('config schema', () => { "enabled": true, "order": 1, "realm": "saml1", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -730,6 +954,7 @@ describe('config schema', () => { "enabled": true, "order": 2, "realm": "saml2", + "session": Object {}, "showInSelector": true, "useRelayStateDeepLink": false, }, @@ -1089,4 +1314,314 @@ describe('createConfig()', () => { '[audit]: xpack.security.audit.ignore_filters can only be used with the ECS audit logger. To enable the ECS audit logger, specify where you want to write the audit events using xpack.security.audit.appender.' ); }); + + describe('#getExpirationTimeouts', () => { + function createMockConfig(config: Record = {}) { + return createConfig(ConfigSchema.validate(config), loggingSystemMock.createLogger(), { + isTLSEnabled: false, + }); + } + + it('returns default values if neither global nor provider specific settings are set', async () => { + expect(createMockConfig().session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + + it('correctly handles explicitly disabled global settings', async () => { + expect( + createMockConfig({ + session: { idleTimeout: null, lifespan: null }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 0, lifespan: 0 }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + + it('falls back to the global settings if provider does not override them', async () => { + expect( + createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({ + type: 'basic', + name: 'basic1', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": null, + } + `); + + expect( + createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({ + type: 'basic', + name: 'basic1', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.456S", + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 123, lifespan: 456 }, + }).session.getExpirationTimeouts({ type: 'basic', name: 'basic1' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.456S", + } + `); + }); + + it('falls back to the global settings if provider is not known', async () => { + expect( + createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({ + type: 'some type', + name: 'some name', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": null, + } + `); + + expect( + createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({ + type: 'some type', + name: 'some name', + }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.456S", + } + `); + + expect( + createMockConfig({ + session: { idleTimeout: 123, lifespan: 456 }, + }).session.getExpirationTimeouts({ type: 'some type', name: 'some name' }) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.456S", + } + `); + }); + + it('uses provider overrides if specified (only idle timeout)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } }, + }, + }, + session: { idleTimeout: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": null, + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": null, + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 332211 } } }, + }, + }, + session: { idleTimeout: 123 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": null, + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": null, + } + `); + }); + + it('uses provider overrides if specified (only lifespan)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { lifespan: 654 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { lifespan: 665544 } } }, + }, + }, + session: { lifespan: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.654S", + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT11M5.544S", + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { lifespan: 654 } } }, + saml: { saml1: { order: 1, realm: 'saml-realm', session: { idleTimeout: 665544 } } }, + }, + }, + session: { lifespan: 456 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": "PT0.654S", + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT11M5.544S", + "lifespan": "PT0.456S", + } + `); + }); + + it('uses provider overrides if specified (both idle timeout and lifespan)', async () => { + const configWithoutGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 332211, lifespan: 665544 }, + }, + }, + }, + }, + session: { idleTimeout: null, lifespan: null }, + }); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.654S", + } + `); + expect(configWithoutGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": "PT11M5.544S", + } + `); + + const configWithGlobal = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: 321, lifespan: 654 } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 332211, lifespan: 665544 }, + }, + }, + }, + }, + session: { idleTimeout: 123, lifespan: 456 }, + }); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.321S", + "lifespan": "PT0.654S", + } + `); + expect(configWithGlobal.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT5M32.211S", + "lifespan": "PT11M5.544S", + } + `); + }); + + it('uses provider overrides if disabled (both idle timeout and lifespan)', async () => { + const config = createMockConfig({ + authc: { + providers: { + basic: { basic1: { order: 0, session: { idleTimeout: null, lifespan: null } } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: 0, lifespan: 0 }, + }, + }, + }, + }, + session: { idleTimeout: 123, lifespan: 456 }, + }); + expect(config.session.getExpirationTimeouts({ type: 'basic', name: 'basic1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + expect(config.session.getExpirationTimeouts({ type: 'saml', name: 'saml1' })) + .toMatchInlineSnapshot(` + Object { + "idleTimeout": null, + "lifespan": null, + } + `); + }); + }); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 80b46a67ce0115..4da0a8598309a3 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -5,11 +5,24 @@ */ import crypto from 'crypto'; +import type { Duration } from 'moment'; import { schema, Type, TypeOf } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { Logger, config as coreConfig } from '../../../../src/core/server'; +import type { AuthenticationProvider } from '../common/types'; export type ConfigType = ReturnType; +type RawConfigType = TypeOf; + +interface ProvidersCommonConfigType { + enabled: Type; + showInSelector: Type; + order: Type; + description?: Type; + hint?: Type; + icon?: Type; + session?: Type<{ idleTimeout?: Duration | null; lifespan?: Duration | null }>; +} const providerOptionsSchema = (providerType: string, optionsSchema: Type) => schema.conditional( @@ -21,10 +34,6 @@ const providerOptionsSchema = (providerType: string, optionsSchema: Type) = schema.never() ); -type ProvidersCommonConfigType = Record< - 'enabled' | 'showInSelector' | 'order' | 'description' | 'hint' | 'icon', - Type ->; function getCommonProviderSchemaProperties(overrides: Partial = {}) { return { enabled: schema.boolean({ defaultValue: true }), @@ -34,6 +43,10 @@ function getCommonProviderSchemaProperties(overrides: Partial, + config: RawConfigType, logger: Logger, { isTLSEnabled }: { isTLSEnabled: boolean } ) { @@ -314,7 +328,33 @@ export function createConfig( sortedProviders: Object.freeze(sortedProviders), http: config.authc.http, }, + session: getSessionConfig(config.session, providers), encryptionKey, secureCookies, }; } + +function getSessionConfig(session: RawConfigType['session'], providers: ProvidersConfigType) { + return { + cleanupInterval: session.cleanupInterval, + getExpirationTimeouts({ type, name }: AuthenticationProvider) { + // Both idle timeout and lifespan from the provider specific session config can have three + // possible types of values: `Duration`, `null` and `undefined`. The `undefined` type means that + // provider doesn't override session config and we should fall back to the global one instead. + const providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session; + + const [idleTimeout, lifespan] = [ + [session.idleTimeout, providerSessionConfig?.idleTimeout], + [session.lifespan, providerSessionConfig?.lifespan], + ].map(([globalTimeout, providerTimeout]) => { + const timeout = providerTimeout === undefined ? globalTimeout ?? null : providerTimeout; + return timeout && timeout.asMilliseconds() > 0 ? timeout : null; + }); + + return { + idleTimeout, + lifespan, + }; + }, + }; +} diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index 5e0f0a6005fb20..c66b5f985cb33f 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -195,7 +195,7 @@ describe('Change password', () => { it('successfully changes own password if provided old password is correct for non-basic provider.', async () => { const mockUser = mockAuthenticatedUser({ username: 'user', - authentication_provider: 'token1', + authentication_provider: { type: 'token', name: 'token1' }, }); authc.getCurrentUser.mockReturnValue(mockUser); authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockUser)); diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index a85369a6f40323..757b1aaeddcbcf 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -4,16 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import nodeCrypto, { Crypto } from '@elastic/node-crypto'; -import type { PublicMethodsOf } from '@kbn/utility-types'; import { promisify } from 'util'; import { randomBytes, createHash } from 'crypto'; -import { Duration } from 'moment'; -import { KibanaRequest, Logger } from '../../../../../src/core/server'; -import { AuthenticationProvider } from '../../common/types'; -import { ConfigType } from '../config'; -import { SessionIndex, SessionIndexValue } from './session_index'; -import { SessionCookie } from './session_cookie'; +import nodeCrypto, { Crypto } from '@elastic/node-crypto'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { KibanaRequest, Logger } from '../../../../../src/core/server'; +import type { AuthenticationProvider } from '../../common/types'; +import type { ConfigType } from '../config'; +import type { SessionIndex, SessionIndexValue } from './session_index'; +import type { SessionCookie } from './session_cookie'; /** * The shape of the value that represents user's session information. @@ -86,21 +85,6 @@ const SID_BYTE_LENGTH = 32; const AAD_BYTE_LENGTH = 32; export class Session { - /** - * Session idle timeout in ms. If `null`, a session will stay active until its max lifespan is reached. - */ - private readonly idleTimeout: Duration | null; - - /** - * Timeout after which idle timeout property is updated in the index. - */ - private readonly idleIndexUpdateTimeout: number | null; - - /** - * Session max lifespan in ms. If `null` session may live indefinitely. - */ - private readonly lifespan: Duration | null; - /** * Used to encrypt and decrypt portion of the session value using configured encryption key. */ @@ -113,14 +97,6 @@ export class Session { constructor(private readonly options: Readonly) { this.crypto = nodeCrypto({ encryptionKey: this.options.config.encryptionKey }); - this.idleTimeout = this.options.config.session.idleTimeout; - this.lifespan = this.options.config.session.lifespan; - - // The timeout after which we update index is two times longer than configured idle timeout - // since index updates are costly and we want to minimize them. - this.idleIndexUpdateTimeout = this.options.config.session.idleTimeout - ? this.options.config.session.idleTimeout.asMilliseconds() * 2 - : null; } /** @@ -194,7 +170,7 @@ export class Session { const sessionLogger = this.getLoggerForSID(sid); sessionLogger.debug('Creating a new session.'); - const sessionExpirationInfo = this.calculateExpiry(); + const sessionExpirationInfo = this.calculateExpiry(sessionValue.provider); const { username, state, ...publicSessionValue } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is @@ -227,7 +203,10 @@ export class Session { return null; } - const sessionExpirationInfo = this.calculateExpiry(sessionCookieValue.lifespanExpiration); + const sessionExpirationInfo = this.calculateExpiry( + sessionValue.provider, + sessionCookieValue.lifespanExpiration + ); const { username, state, metadata, ...publicSessionInfo } = sessionValue; // First try to store session in the index and only then in the cookie to make sure cookie is @@ -276,7 +255,10 @@ export class Session { // We calculate actual expiration values based on the information extracted from the portion of // the session value that is stored in the cookie since it always contains the most recent value. - const sessionExpirationInfo = this.calculateExpiry(sessionCookieValue.lifespanExpiration); + const sessionExpirationInfo = this.calculateExpiry( + sessionValue.provider, + sessionCookieValue.lifespanExpiration + ); if ( sessionExpirationInfo.idleTimeoutExpiration === sessionValue.idleTimeoutExpiration && sessionExpirationInfo.lifespanExpiration === sessionValue.lifespanExpiration @@ -311,17 +293,24 @@ export class Session { 'Session lifespan configuration has changed, session index will be updated.' ); updateSessionIndex = true; - } else if ( - this.idleIndexUpdateTimeout !== null && - this.idleIndexUpdateTimeout < - sessionExpirationInfo.idleTimeoutExpiration! - - sessionValue.metadata.index.idleTimeoutExpiration! - ) { - // 3. If idle timeout was updated a while ago. - sessionLogger.debug( - 'Session idle timeout stored in the index is too old and will be updated.' + } else { + // The timeout after which we update index is two times longer than configured idle timeout + // since index updates are costly and we want to minimize them. + const { idleTimeout } = this.options.config.session.getExpirationTimeouts( + sessionValue.provider ); - updateSessionIndex = true; + if ( + idleTimeout !== null && + idleTimeout.asMilliseconds() * 2 < + sessionExpirationInfo.idleTimeoutExpiration! - + sessionValue.metadata.index.idleTimeoutExpiration! + ) { + // 3. If idle timeout was updated a while ago. + sessionLogger.debug( + 'Session idle timeout stored in the index is too old and will be updated.' + ); + updateSessionIndex = true; + } } // First try to store session in the index and only then in the cookie to make sure cookie is @@ -375,18 +364,21 @@ export class Session { } private calculateExpiry( + provider: AuthenticationProvider, currentLifespanExpiration?: number | null ): { idleTimeoutExpiration: number | null; lifespanExpiration: number | null } { const now = Date.now(); + const { idleTimeout, lifespan } = this.options.config.session.getExpirationTimeouts(provider); + // if we are renewing an existing session, use its `lifespanExpiration` -- otherwise, set this value // based on the configured server `lifespan`. // note, if the server had a `lifespan` set and then removes it, remove `lifespanExpiration` on renewed sessions // also, if the server did not have a `lifespan` set and then adds it, add `lifespanExpiration` on renewed sessions const lifespanExpiration = - currentLifespanExpiration && this.lifespan + currentLifespanExpiration && lifespan ? currentLifespanExpiration - : this.lifespan && now + this.lifespan.asMilliseconds(); - const idleTimeoutExpiration = this.idleTimeout && now + this.idleTimeout.asMilliseconds(); + : lifespan && now + lifespan.asMilliseconds(); + const idleTimeoutExpiration = idleTimeout && now + idleTimeout.asMilliseconds(); return { idleTimeoutExpiration, lifespanExpiration }; } diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index cba63412bf5022..1dd47c7ff66e84 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -194,8 +194,39 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { range: { idleTimeoutExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }, ], }, }, @@ -226,9 +257,49 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }, - { range: { idleTimeoutExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a particular provider that are expired based on the idle timeout. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }, ], }, }, @@ -260,9 +331,43 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, ], }, }, @@ -294,10 +399,179 @@ describe('Session index', () => { query: { bool: { should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. { range: { lifespanExpiration: { lte: now } } }, - { bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }, - { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a particular provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a particular provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * idleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + }, + }); + }); + + it('when both `lifespan` and `idleTimeout` are configured and multiple providers are enabled', async () => { + const globalIdleTimeout = 123; + const samlIdleTimeout = 33221; + sessionIndex = new SessionIndex({ + logger: loggingSystemMock.createLogger(), + kibanaIndexName: '.kibana_some_tenant', + config: createConfig( + ConfigSchema.validate({ + session: { idleTimeout: globalIdleTimeout, lifespan: 456 }, + authc: { + providers: { + basic: { basic1: { order: 0 } }, + saml: { + saml1: { + order: 1, + realm: 'saml-realm', + session: { idleTimeout: samlIdleTimeout }, + }, + }, + }, + }, + }), + loggingSystemMock.createLogger(), + { isTLSEnabled: false } + ), + clusterClient: mockClusterClient, + }); + + await sessionIndex.cleanUp(); + + expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockClusterClient.callAsInternalUser).toHaveBeenCalledWith('deleteByQuery', { + index: indexName, + refresh: 'wait_for', + ignore: [409, 404], + body: { + query: { + bool: { + should: [ + // All expired sessions based on the lifespan, no matter which provider they belong to. + { range: { lifespanExpiration: { lte: now } } }, + // All sessions that belong to the providers that aren't configured. + { + bool: { + must_not: { + bool: { + should: [ + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + }, + }, + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }, + }, + }, + // The sessions that belong to a Basic provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a Basic provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'basic' } }, + { term: { 'provider.name': 'basic1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * globalIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, + // The sessions that belong to a SAML provider but don't have a configured lifespan. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + must_not: { exists: { field: 'lifespanExpiration' } }, + }, + }, + // The sessions that belong to a SAML provider that are either expired based on the idle timeout + // or don't have it configured at all. + { + bool: { + must: [ + { term: { 'provider.type': 'saml' } }, + { term: { 'provider.name': 'saml1' } }, + ], + should: [ + { range: { idleTimeoutExpiration: { lte: now - 3 * samlIdleTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ], + minimum_should_match: 1, + }, + }, ], }, }, diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index ee503acc0d3a4e..96fff41d575034 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; -import { AuthenticationProvider } from '../../common/types'; -import { ConfigType } from '../config'; +import type { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; +import type { AuthenticationProvider } from '../../common/types'; +import type { ConfigType } from '../config'; export interface SessionIndexOptions { readonly clusterClient: ILegacyClusterClient; readonly kibanaIndexName: string; - readonly config: Pick; + readonly config: Pick; readonly logger: Logger; } @@ -120,12 +120,6 @@ export class SessionIndex { */ private readonly indexName = `${this.options.kibanaIndexName}_security_session_${SESSION_INDEX_TEMPLATE_VERSION}`; - /** - * Timeout after which session with the expired idle timeout _may_ be removed from the index - * during regular cleanup routine. - */ - private readonly idleIndexCleanupTimeout: number | null; - /** * Promise that tracks session index initialization process. We'll need to get rid of this as soon * as Core provides support for plugin statuses (https://github.com/elastic/kibana/issues/41983). @@ -134,14 +128,7 @@ export class SessionIndex { */ private indexInitialization?: Promise; - constructor(private readonly options: Readonly) { - // This timeout is intentionally larger than the `idleIndexUpdateTimeout` (idleTimeout * 2) - // configured in `Session` to be sure that the session value is definitely expired and may be - // safely cleaned up. - this.idleIndexCleanupTimeout = this.options.config.session.idleTimeout - ? this.options.config.session.idleTimeout.asMilliseconds() * 3 - : null; - } + constructor(private readonly options: Readonly) {} /** * Retrieves session value with the specified ID from the index. If session value isn't found @@ -353,26 +340,62 @@ export class SessionIndex { this.options.logger.debug(`Running cleanup routine.`); const now = Date.now(); + const providersSessionConfig = this.options.config.authc.sortedProviders.map((provider) => { + return { + boolQuery: { + bool: { + must: [ + { term: { 'provider.type': provider.type } }, + { term: { 'provider.name': provider.name } }, + ], + }, + }, + ...this.options.config.session.getExpirationTimeouts(provider), + }; + }); // Always try to delete sessions with expired lifespan (even if it's not configured right now). const deleteQueries: object[] = [{ range: { lifespanExpiration: { lte: now } } }]; - // If lifespan is configured we should remove any sessions that were created without one. - if (this.options.config.session.lifespan) { - deleteQueries.push({ bool: { must_not: { exists: { field: 'lifespanExpiration' } } } }); - } + // If session belongs to a not configured provider we should also remove it. + deleteQueries.push({ + bool: { + must_not: { + bool: { + should: providersSessionConfig.map(({ boolQuery }) => boolQuery), + minimum_should_match: 1, + }, + }, + }, + }); - // If idle timeout is configured we should delete all sessions without specified idle timeout - // or if that session hasn't been updated for a while meaning that session is expired. - if (this.idleIndexCleanupTimeout) { - deleteQueries.push( - { range: { idleTimeoutExpiration: { lte: now - this.idleIndexCleanupTimeout } } }, - { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } } - ); - } else { - // Otherwise just delete all expired sessions that were previously created with the idle - // timeout. - deleteQueries.push({ range: { idleTimeoutExpiration: { lte: now } } }); + for (const { boolQuery, lifespan, idleTimeout } of providersSessionConfig) { + // If lifespan is configured we should remove any sessions that were created without one. + if (lifespan) { + deleteQueries.push({ + bool: { ...boolQuery.bool, must_not: { exists: { field: 'lifespanExpiration' } } }, + }); + } + + // This timeout is intentionally larger than the timeout used in `Session` to update idle + // timeout in the session index (idleTimeout * 2) to be sure that the session value is + // definitely expired and may be safely cleaned up. + const idleIndexCleanupTimeout = idleTimeout ? idleTimeout.asMilliseconds() * 3 : null; + deleteQueries.push({ + bool: { + ...boolQuery.bool, + // If idle timeout is configured we should delete all sessions without specified idle timeout + // or if that session hasn't been updated for a while meaning that session is expired. Otherwise + // just delete all expired sessions that were previously created with the idle timeout. + should: idleIndexCleanupTimeout + ? [ + { range: { idleTimeoutExpiration: { lte: now - idleIndexCleanupTimeout } } }, + { bool: { must_not: { exists: { field: 'idleTimeoutExpiration' } } } }, + ] + : [{ range: { idleTimeoutExpiration: { lte: now } } }], + minimum_should_match: 1, + }, + }); } try { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 767a2616a4c7e3..8c423c663a4e8c 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -39,6 +39,9 @@ export const FILTERS_GLOBAL_HEIGHT = 109; // px export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled'; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51'; export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*'; +export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true; +export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms +export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms export enum SecurityPageName { detections = 'detections', @@ -74,6 +77,9 @@ export const DEFAULT_INDEX_PATTERN = [ /** This Kibana Advanced Setting enables the `Security news` feed widget */ export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed'; +/** This Kibana Advanced Setting sets the auto refresh interval for the detections all rules table */ +export const DEFAULT_RULES_TABLE_REFRESH_SETTING = 'securitySolution:rulesTableRefresh'; + /** This Kibana Advanced Setting specifies the URL of the News feed widget */ export const NEWS_FEED_URL_SETTING = 'securitySolution:newsFeedUrl'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts index 6ffbf4e4c8d4ca..1b0417cf59bc28 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts @@ -48,6 +48,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -130,6 +132,8 @@ export const addPrepackagedRulesSchema = t.intersection([ threat_query, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts index a4f002b589ef55..1b6a8d6f27762e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts @@ -1702,5 +1702,23 @@ describe('create rules schema', () => { expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); }); + + test('You can set a threat query, index, mapping, filters, concurrent_searches, items_per_search with a when creating a rule', () => { + const payload: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + const decoded = createRulesSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected: CreateRulesSchemaDecoded = { + ...getCreateThreatMatchRulesSchemaDecodedMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts index d8e7614fcb8401..2fe52bbe470a52 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts @@ -49,6 +49,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -126,6 +128,8 @@ export const createRulesSchema = t.intersection([ threat_filters, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults "undefined" if not set during decode + items_per_search, // defaults "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts index 75ad92578318ce..a78b41cd0da187 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts @@ -125,4 +125,36 @@ describe('create_rules_type_dependents', () => { const errors = createRuleValidateTypeDependents(schema); expect(errors).toEqual([]); }); + + test('validates that both "items_per_search" and "concurrent_searches" works when together', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + items_per_search: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([]); + }); + + test('does NOT validate when only "items_per_search" is present', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + items_per_search: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([ + 'when "items_per_search" exists, "concurrent_searches" must also exist', + ]); + }); + + test('does NOT validate when only "concurrent_searches" is present', () => { + const schema: CreateRulesSchema = { + ...getCreateThreatMatchRulesSchemaMock(), + concurrent_searches: 10, + }; + const errors = createRuleValidateTypeDependents(schema); + expect(errors).toEqual([ + 'when "concurrent_searches" exists, "items_per_search" must also exist', + ]); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts index c2a41005ebf4d6..c93b0f0b14f6ad 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts @@ -110,17 +110,23 @@ export const validateThreshold = (rule: CreateRulesSchema): string[] => { export const validateThreatMapping = (rule: CreateRulesSchema): string[] => { let errors: string[] = []; if (isThreatMatchRule(rule.type)) { - if (!rule.threat_mapping) { + if (rule.threat_mapping == null) { errors = ['when "type" is "threat_match", "threat_mapping" is required', ...errors]; } else if (rule.threat_mapping.length === 0) { errors = ['threat_mapping" must have at least one element', ...errors]; } - if (!rule.threat_query) { + if (rule.threat_query == null) { errors = ['when "type" is "threat_match", "threat_query" is required', ...errors]; } - if (!rule.threat_index) { + if (rule.threat_index == null) { errors = ['when "type" is "threat_match", "threat_index" is required', ...errors]; } + if (rule.concurrent_searches == null && rule.items_per_search != null) { + errors = ['when "items_per_search" exists, "concurrent_searches" must also exist', ...errors]; + } + if (rule.concurrent_searches != null && rule.items_per_search == null) { + errors = ['when "concurrent_searches" exists, "items_per_search" must also exist', ...errors]; + } } return errors; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts index 852394b74767b7..4f28c469238658 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts @@ -55,6 +55,8 @@ import { } from '../common/schemas'; import { threat_index, + items_per_search, + concurrent_searches, threat_query, threat_filters, threat_mapping, @@ -149,6 +151,8 @@ export const importRulesSchema = t.intersection([ threat_query, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts index f4dce5c7ac05f8..45fcfbaa3c76aa 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts @@ -50,6 +50,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -109,6 +111,8 @@ export const patchRulesSchema = t.exact( threat_filters, threat_mapping, threat_language, + concurrent_searches, + items_per_search, }) ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts index b0cd8b1c53688a..5d759fc12cd528 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts @@ -51,6 +51,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -134,6 +136,8 @@ export const updateRulesSchema = t.intersection([ threat_filters, // defaults to "undefined" if not set during decode threat_index, // defaults to "undefined" if not set during decode threat_language, // defaults "undefined" if not set during decode + concurrent_searches, // defaults to "undefined" if not set during decode + items_per_search, // defaults to "undefined" if not set during decode }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index 82675768a11b7c..3508526e182d77 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -762,9 +762,9 @@ describe('rules_schema', () => { expect(fields).toEqual(expected); }); - test('should return 5 fields for a rule of type "threat_match"', () => { + test('should return 8 fields for a rule of type "threat_match"', () => { const fields = addThreatMatchFields({ type: 'threat_match' }); - expect(fields.length).toEqual(6); + expect(fields.length).toEqual(8); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts index e85beddf0e51e9..0f7d04763a36f1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts @@ -63,6 +63,8 @@ import { } from '../common/schemas'; import { threat_index, + concurrent_searches, + items_per_search, threat_query, threat_filters, threat_mapping, @@ -144,6 +146,8 @@ export const dependentRulesSchema = t.partial({ threat_filters, threat_index, threat_query, + concurrent_searches, + items_per_search, threat_mapping, threat_language, }); @@ -282,6 +286,12 @@ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })), t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })), t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), + t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })), + t.exact( + t.partial({ + items_per_search: dependentRulesSchema.props.items_per_search, + }) + ), ]; } else { return []; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts index 63d593ea84e67f..d8f61e4309b17a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.test.ts @@ -5,6 +5,8 @@ */ import { + concurrent_searches, + items_per_search, ThreatMapping, threatMappingEntries, ThreatMappingEntries, @@ -33,7 +35,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual(payload); }); - test('it should NOT validate an extra entry item', () => { + test('it should fail validation with an extra entry item', () => { const payload: ThreatMappingEntries & Array<{ extra: string }> = [ { field: 'field.one', @@ -50,7 +52,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a non string', () => { + test('it should fail validation with a non string', () => { const payload = ([ { field: 5, @@ -66,7 +68,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a wrong type', () => { + test('it should fail validation with a wrong type', () => { const payload = ([ { field: 'field.one', @@ -107,7 +109,7 @@ describe('threat_mapping', () => { }); }); - test('it should NOT validate an extra key', () => { + test('it should fail validate with an extra key', () => { const payload: ThreatMapping & Array<{ extra: string }> = [ { entries: [ @@ -129,7 +131,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate an extra inner entry', () => { + test('it should fail validate with an extra inner entry', () => { const payload: ThreatMapping & Array<{ entries: Array<{ extra: string }> }> = [ { entries: [ @@ -151,7 +153,7 @@ describe('threat_mapping', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate an extra inner entry with the wrong data type', () => { + test('it should fail validate with an extra inner entry with the wrong data type', () => { const payload = ([ { entries: [ @@ -173,4 +175,48 @@ describe('threat_mapping', () => { ]); expect(message.schema).toEqual({}); }); + + test('it should fail validation when concurrent_searches is < 0', () => { + const payload = -1; + const decoded = concurrent_searches.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when concurrent_searches is 0', () => { + const payload = 0; + const decoded = concurrent_searches.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when items_per_search is 0', () => { + const payload = 0; + const decoded = items_per_search.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should fail validation when items_per_search is < 0', () => { + const payload = -1; + const decoded = items_per_search.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "-1" supplied to "PositiveIntegerGreaterThanZero"', + ]); + expect(message.schema).toEqual({}); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts index a1be6485f596b7..dec8ddd0001324 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts @@ -9,6 +9,7 @@ import * as t from 'io-ts'; import { language } from '../common/schemas'; import { NonEmptyString } from './non_empty_string'; +import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; export const threat_query = t.string; export type ThreatQuery = t.TypeOf; @@ -55,3 +56,13 @@ export const threat_language = t.union([language, t.undefined]); export type ThreatLanguage = t.TypeOf; export const threatLanguageOrUndefined = t.union([threat_language, t.undefined]); export type ThreatLanguageOrUndefined = t.TypeOf; + +export const concurrent_searches = PositiveIntegerGreaterThanZero; +export type ConcurrentSearches = t.TypeOf; +export const concurrentSearchesOrUndefined = t.union([concurrent_searches, t.undefined]); +export type ConcurrentSearchesOrUndefined = t.TypeOf; + +export const items_per_search = PositiveIntegerGreaterThanZero; +export type ItemsPerSearch = t.TypeOf; +export const itemsPerSearchOrUndefined = t.union([items_per_search, t.undefined]); +export type ItemsPerSearchOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts index 3fa304ab7cf190..6a62caecfaa675 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules.spec.ts @@ -10,6 +10,7 @@ import { RULE_SWITCH, SECOND_RULE, SEVENTH_RULE, + RULE_AUTO_REFRESH_IDLE_MODAL, } from '../screens/alerts_detection_rules'; import { @@ -19,12 +20,17 @@ import { } from '../tasks/alerts'; import { activateRule, + checkAllRulesIdleModal, + checkAutoRefresh, + dismissAllRulesIdleModal, + resetAllRulesIdleModalTimeout, sortByActivatedRules, waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, waitForRuleToBeActivated, } from '../tasks/alerts_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants'; import { DETECTIONS_URL } from '../urls/navigation'; @@ -35,6 +41,7 @@ describe('Alerts detection rules', () => { after(() => { esArchiverUnload('prebuilt_rules_loaded'); + cy.clock().invoke('restore'); }); it('Sorts by activated rules', () => { @@ -75,4 +82,34 @@ describe('Alerts detection rules', () => { }); }); }); + + it('Auto refreshes rules', () => { + cy.clock(Date.now()); + + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + waitForAlertsPanelToBeLoaded(); + waitForAlertsIndexToBeCreated(); + goToManageAlertsDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + + // mock 1 minute passing to make sure refresh + // is conducted + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + + // mock 45 minutes passing to check that idle modal shows + // and refreshing is paused + checkAllRulesIdleModal('be.visible'); + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.be.visible'); + + // clicking on modal to continue, should resume refreshing + dismissAllRulesIdleModal(); + checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + + // if mouse movement detected, idle modal should not + // show after 45 min + resetAllRulesIdleModalTimeout(); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.exist'); + + cy.clock().invoke('restore'); + }); }); 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 13fa9592469e4b..9eb49c19c23f6c 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 @@ -142,10 +142,11 @@ describe('Events Viewer', () => { }); }); - context.skip('Events columns', () => { + context('Events columns', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); + cy.scrollTo('bottom'); waitsForEventsToBeLoaded(); }); @@ -160,9 +161,8 @@ describe('Events Viewer', () => { const expectedOrderAfterDragAndDrop = 'message@timestamphost.nameevent.moduleevent.datasetevent.actionuser.namesource.ipdestination.ip'; - cy.scrollTo('bottom'); cy.get(HEADERS_GROUP).invoke('text').should('equal', originalColumnOrder); - dragAndDropColumn({ column: 0, newPosition: 1 }); + dragAndDropColumn({ column: 0, newPosition: 0 }); cy.get(HEADERS_GROUP).invoke('text').should('equal', expectedOrderAfterDragAndDrop); }); }); 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 index 383ebe22205859..d518f9e42f21f5 100644 --- 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 @@ -13,7 +13,8 @@ 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', () => { +// Failing: See https://github.com/elastic/kibana/issues/75794 +describe.skip('persistent timeline', () => { before(() => { loginAndWaitForPage(HOSTS_URL); openEvents(); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 0d0ea8460edf11..5ac8cd8f6cc9f7 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -10,7 +10,7 @@ export const CREATE_NEW_RULE_BTN = '[data-test-subj="create-new-rule"]'; export const COLLAPSED_ACTION_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]'; -export const CUSTOM_RULES_BTN = '[data-test-subj="show-custom-rules-filter-button"]'; +export const CUSTOM_RULES_BTN = '[data-test-subj="showCustomRulesFilterButton"]'; export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]'; @@ -18,7 +18,7 @@ export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]'; export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; -export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]'; +export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]'; export const EXPORT_ACTION_BTN = '[data-test-subj="exportRuleAction"]'; @@ -31,7 +31,7 @@ export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOADING_INITIAL_PREBUILT_RULES_TABLE = '[data-test-subj="initialLoadingPanelAllRulesTable"]'; -export const LOADING_SPINNER = '[data-test-subj="loading-spinner"]'; +export const ASYNC_LOADING_PROGRESS = '[data-test-subj="loadingRulesInfoProgress"]'; export const NEXT_BTN = '[data-test-subj="pagination-button-next"]'; @@ -64,3 +64,7 @@ export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; export const SORT_RULES_BTN = '[data-test-subj="tableHeaderSortButton"]'; export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]'; + +export const RULE_AUTO_REFRESH_IDLE_MODAL = '[data-test-subj="allRulesIdleModal"]'; + +export const RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE = '[data-test-subj="allRulesIdleModal"] button'; 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 0434de7bff88ea..cf507924a753fe 100644 --- a/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/screens/hosts/events.ts @@ -34,3 +34,6 @@ export const LOAD_MORE = '[data-test-subj="events-viewer-panel"] [data-test-subj="TimelineMoreButton"'; export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; + +export const EVENTS_VIEWER_PAGINATION = + '[data-test-subj="events-viewer-panel"] [data-test-subj="timeline-pagination"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 1c430e12b6b734..d4602dcd16db80 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -13,7 +13,6 @@ import { DELETE_RULE_BULK_BTN, LOAD_PREBUILT_RULES_BTN, LOADING_INITIAL_PREBUILT_RULES_TABLE, - LOADING_SPINNER, PAGINATION_POPOVER_BTN, RELOAD_PREBUILT_RULES_BTN, RULE_CHECKBOX, @@ -26,6 +25,9 @@ import { EXPORT_ACTION_BTN, EDIT_RULE_ACTION_BTN, NEXT_BTN, + ASYNC_LOADING_PROGRESS, + RULE_AUTO_REFRESH_IDLE_MODAL, + RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE, } from '../screens/alerts_detection_rules'; import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details'; @@ -66,8 +68,8 @@ export const exportFirstRule = () => { export const filterByCustomRules = () => { cy.get(CUSTOM_RULES_BTN).click({ force: true }); - cy.get(LOADING_SPINNER).should('exist'); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('not.exist'); }; export const goToCreateNewRule = () => { @@ -119,6 +121,32 @@ export const waitForRuleToBeActivated = () => { }; export const waitForRulesToBeLoaded = () => { - cy.get(LOADING_SPINNER).should('exist'); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('exist'); + cy.get(ASYNC_LOADING_PROGRESS).should('not.exist'); +}; + +// when using, ensure you've called cy.clock prior in test +export const checkAutoRefresh = (ms: number, condition: string) => { + cy.get(ASYNC_LOADING_PROGRESS).should('not.be.visible'); + cy.tick(ms); + cy.get(ASYNC_LOADING_PROGRESS).should(condition); +}; + +export const dismissAllRulesIdleModal = () => { + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE) + .eq(1) + .should('exist') + .click({ force: true, multiple: true }); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.be.visible'); +}; + +export const checkAllRulesIdleModal = (condition: string) => { + cy.tick(2700000); + cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should(condition); +}; + +export const resetAllRulesIdleModalTimeout = () => { + cy.tick(2000000); + cy.window().trigger('mousemove', { force: true }); + cy.tick(700000); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index e16db545999812..bb009f34b02d62 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -23,14 +23,14 @@ export const drag = (subject: JQuery) => { clientY: subjectLocation.top, force: true, }) - .wait(3000) + .wait(300) .trigger('mousemove', { button: primaryButton, clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, clientY: subjectLocation.top, force: true, }) - .wait(3000); + .wait(300); }; /** Drags the subject being dragged on the specified drop target, but does not drop it */ @@ -42,11 +42,17 @@ export const dragWithoutDrop = (dropTarget: JQuery) => { /** "Drops" the subject being dragged on the specified drop target */ export const drop = (dropTarget: JQuery) => { + const targetLocation = dropTarget[0].getBoundingClientRect(); cy.wrap(dropTarget) - .trigger('mousemove', { button: primaryButton, force: true }) - .wait(3000) + .trigger('mousemove', { + button: primaryButton, + clientX: targetLocation.left, + clientY: targetLocation.top, + force: true, + }) + .wait(300) .trigger('mouseup', { force: true }) - .wait(3000); + .wait(300); }; export const reload = (afterReload: () => void) => { 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 226178cd92f18c..401a78767ac578 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/hosts/events.ts @@ -8,6 +8,7 @@ import { drag, drop } from '../common'; import { CLOSE_MODAL, EVENTS_VIEWER_FIELDS_BUTTON, + EVENTS_VIEWER_PAGINATION, FIELDS_BROWSER_CONTAINER, HOST_GEO_CITY_NAME_CHECKBOX, HOST_GEO_COUNTRY_NAME_CHECKBOX, @@ -16,6 +17,7 @@ import { SERVER_SIDE_EVENT_COUNT, } from '../../screens/hosts/events'; import { DRAGGABLE_HEADER } from '../../screens/timeline'; +import { REFRESH_BUTTON } from '../../screens/security_header'; export const addsHostGeoCityNameToHeader = () => { cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ @@ -53,7 +55,9 @@ export const opensInspectQueryModal = () => { }; export const waitsForEventsToBeLoaded = () => { - cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0'); + cy.get(SERVER_SIDE_EVENT_COUNT).should('not.have.text', '0'); + cy.get(REFRESH_BUTTON).should('not.have.text', 'Updating'); + cy.get(EVENTS_VIEWER_PAGINATION).should('exist'); }; export const dragAndDropColumn = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index f2d2d23d60fb13..d3d20c71835707 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -7,7 +7,9 @@ exports[`HeaderSection it renders 1`] = ` - + = ({ @@ -57,10 +58,11 @@ const HeaderSectionComponent: React.FC = ({ title, titleSize = 'm', tooltip, + growLeftSplit = true, }) => (
- + diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx new file mode 100644 index 00000000000000..db42794448c533 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/index.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mount } from 'enzyme'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { LastUpdatedAt } from './'; + +describe('LastUpdatedAt', () => { + beforeEach(() => { + Date.now = jest.fn().mockReturnValue(1603995369774); + }); + + test('it renders correct relative time', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(' Updated 2 minutes ago'); + }); + + test('it only renders icon if "compact" is true', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(''); + expect(wrapper.find('[data-test-subj="last-updated-at-clock-icon"]').exists()).toBeTruthy(); + }); + + test('it renders updating text if "showUpdating" is true', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(' Updating...'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx b/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx new file mode 100644 index 00000000000000..ef4ff0123dd1cc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/index.tsx @@ -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 { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import React, { useEffect, useMemo, useState } from 'react'; + +import * as i18n from './translations'; + +interface LastUpdatedAtProps { + compact?: boolean; + updatedAt: number; + showUpdating?: boolean; +} + +export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>( + ({ date, prefix, updatedAt }) => ( + <> + {prefix} + { + + } + + ) +); + +Updated.displayName = 'Updated'; + +const prefix = ` ${i18n.UPDATED} `; + +export const LastUpdatedAt = React.memo( + ({ compact = false, updatedAt, showUpdating = false }) => { + const [date, setDate] = useState(Date.now()); + + function tick() { + setDate(Date.now()); + } + + useEffect(() => { + const timerID = setInterval(() => tick(), 10000); + return () => { + clearInterval(timerID); + }; + }, []); + + const updateText = useMemo(() => { + if (showUpdating) { + return {i18n.UPDATING}; + } + + if (!compact) { + return ; + } + + return null; + }, [compact, date, showUpdating, updatedAt]); + + return ( + + + + } + > + + + {updateText} + + + ); + } +); + +LastUpdatedAt.displayName = 'LastUpdatedAt'; diff --git a/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts b/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts new file mode 100644 index 00000000000000..77278563b24d5a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/last_updated/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const UPDATING = i18n.translate('xpack.securitySolution.lastUpdated.updating', { + defaultMessage: 'Updating...', +}); + +export const UPDATED = i18n.translate('xpack.securitySolution.lastUpdated.updated', { + defaultMessage: 'Updated', +}); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 06c152b94cfd82..38ae49ba3b19cf 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -27,6 +27,10 @@ import { DEFAULT_REFRESH_RATE_INTERVAL, DEFAULT_TIME_RANGE, DEFAULT_TO, + DEFAULT_RULES_TABLE_REFRESH_SETTING, + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, } from '../../../../common/constants'; import { StartServices } from '../../../types'; import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage'; @@ -48,6 +52,11 @@ const mockUiSettings: Record = { [DEFAULT_DATE_FORMAT_TZ]: 'UTC', [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', [DEFAULT_DARK_MODE]: false, + [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { + on: DEFAULT_RULE_REFRESH_INTERVAL_ON, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, }; export const createUseUiSettingMock = () => (key: string, defaultValue?: unknown): unknown => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx index 164b1df8463e6f..221963767caade 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx @@ -95,7 +95,7 @@ export const THREAT_MATCH_INDEX_HELPER_TEXT = i18n.translate( export const THREAT_MATCH_REQUIRED = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError', { - defaultMessage: 'At least one threat match is required.', + defaultMessage: 'At least one indicator match is required.', } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index 6800743db738eb..2b03d6dd4de364 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -210,7 +210,7 @@ export const getColumns = ({ getEmptyTagValue() ) : ( - + ); }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index 1a4c2d405dca32..be42d7b3212fd5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -6,13 +6,21 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; import '../../../../../common/mock/match_media'; import '../../../../../common/mock/formatted_relative'; -import { TestProviders } from '../../../../../common/mock'; -import { waitFor } from '@testing-library/react'; import { AllRules } from './index'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; +import { useRules, useRulesStatuses } from '../../../../containers/detection_engine/rules'; +import { TestProviders } from '../../../../../common/mock'; +import { createUseUiSetting$Mock } from '../../../../../common/lib/kibana/kibana_react.mock'; +import { + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, + DEFAULT_RULES_TABLE_REFRESH_SETTING, +} from '../../../../../../common/constants'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -27,66 +35,33 @@ jest.mock('react-router-dom', () => { jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../../common/lib/kibana'); +jest.mock('../../../../containers/detection_engine/rules'); const useKibanaMock = useKibana as jest.Mocked; +const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -jest.mock('./reducer', () => { - return { - allRulesReducer: jest.fn().mockReturnValue(() => ({ - exportRuleIds: [], - filterOptions: { - filter: 'some filter', - sortField: 'some sort field', - sortOrder: 'desc', - }, - loadingRuleIds: [], - loadingRulesAction: null, - pagination: { - page: 1, - perPage: 20, - total: 1, - }, - rules: [ - { - actions: [], - created_at: '2020-02-14T19:49:28.178Z', - created_by: 'elastic', - description: 'jibber jabber', - enabled: false, - false_positives: [], - filters: [], - from: 'now-660s', - id: 'rule-id-1', - immutable: true, - index: ['endgame-*'], - interval: '10m', - language: 'kuery', - max_signals: 100, - name: 'Credential Dumping - Detected - Elastic Endpoint', - output_index: '.siem-signals-default', - query: 'host.name:*', - references: [], - risk_score: 73, - rule_id: '571afc56-5ed9-465d-a2a9-045f099f6e7e', - severity: 'high', - tags: ['Elastic', 'Endpoint'], - threat: [], - throttle: null, - to: 'now', - type: 'query', - updated_at: '2020-02-14T19:49:28.320Z', - updated_by: 'elastic', - version: 1, - }, - ], - selectedRuleIds: [], - })), - }; -}); +describe('AllRules', () => { + const mockRefetchRulesData = jest.fn(); -jest.mock('../../../../containers/detection_engine/rules', () => { - return { - useRules: jest.fn().mockReturnValue([ + beforeEach(() => { + jest.useFakeTimers(); + + mockUseUiSetting$.mockImplementation((key, defaultValue) => { + const useUiSetting$Mock = createUseUiSetting$Mock(); + + return key === DEFAULT_RULES_TABLE_REFRESH_SETTING + ? [ + { + on: DEFAULT_RULE_REFRESH_INTERVAL_ON, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, + jest.fn(), + ] + : useUiSetting$Mock(key, defaultValue); + }); + + (useRules as jest.Mock).mockReturnValue([ false, { page: 1, @@ -126,8 +101,10 @@ jest.mock('../../../../containers/detection_engine/rules', () => { }, ], }, - ]), - useRulesStatuses: jest.fn().mockReturnValue({ + mockRefetchRulesData, + ]); + + (useRulesStatuses as jest.Mock).mockReturnValue({ loading: false, rulesStatuses: [ { @@ -150,21 +127,8 @@ jest.mock('../../../../containers/detection_engine/rules', () => { name: 'Test rule', }, ], - }), - }; -}); - -jest.mock('react-router-dom', () => { - const originalModule = jest.requireActual('react-router-dom'); - - return { - ...originalModule, - useHistory: jest.fn(), - }; -}); + }); -describe('AllRules', () => { - beforeEach(() => { useKibanaMock().services.application.capabilities = { navLinks: {}, management: {}, @@ -172,6 +136,12 @@ describe('AllRules', () => { actions: { show: true }, }; }); + + afterEach(() => { + jest.clearAllTimers(); + jest.clearAllMocks(); + }); + it('renders correctly', () => { const wrapper = shallow( { expect(wrapper.find('[title="All rules"]')).toHaveLength(1); }); + it('it pulls from uiSettings to determine default refresh values', async () => { + mount( + + + + ); + + await waitFor(() => { + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).toHaveBeenCalledTimes(1); + }); + }); + + // refresh functionality largely tested in cypress tests + it('it pulls from storage and does not set an auto refresh interval if storage indicates refresh is paused', async () => { + mockUseUiSetting$.mockImplementation(() => [ + { + on: false, + value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE, + }, + jest.fn(), + ]); + + const wrapper = mount( + + + + ); + + await waitFor(() => { + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + + wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); + + wrapper.find('[data-test-subj="refreshSettingsSwitch"]').first().simulate('click'); + + jest.advanceTimersByTime(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + expect(mockRefetchRulesData).not.toHaveBeenCalled(); + }); + }); + describe('rules tab', () => { - it('renders correctly', async () => { + it('renders all rules tab by default', async () => { const wrapper = mount( { /> ); - const monitoringTab = wrapper.find('[data-test-subj="allRulesTableTab-monitoring"] button'); - monitoringTab.simulate('click'); await waitFor(() => { + const monitoringTab = wrapper.find('[data-test-subj="allRulesTableTab-monitoring"] button'); + monitoringTab.simulate('click'); + wrapper.update(); expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeTruthy(); expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeFalsy(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx index 86b3daddd6c19b..663a4bb242c069 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx @@ -6,15 +6,18 @@ import { EuiBasicTable, - EuiContextMenuPanel, EuiLoadingContent, EuiSpacer, EuiTab, EuiTabs, + EuiProgress, + EuiOverlayMask, + EuiConfirmModal, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; import uuid from 'uuid'; +import { debounce } from 'lodash/fp'; import { useRules, @@ -27,14 +30,7 @@ import { RulesSortingFields, } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../../common/components/header_section'; -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../../../common/components/utility_bar'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; import { useStateToaster } from '../../../../../common/components/toasters'; import { Loader } from '../../../../../common/components/loader'; import { Panel } from '../../../../../common/components/panel'; @@ -55,6 +51,9 @@ import { hasMlLicense } from '../../../../../../common/machine_learning/has_ml_l import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { isBoolean } from '../../../../../common/utils/privileges'; +import { AllRulesUtilityBar } from './utility_bar'; +import { LastUpdatedAt } from '../../../../../common/components/last_updated'; +import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/constants'; const INITIAL_SORT_FIELD = 'enabled'; const initialState: State = { @@ -73,6 +72,9 @@ const initialState: State = { }, rules: [], selectedRuleIds: [], + lastUpdated: 0, + showIdleModal: false, + isRefreshOn: true, }; interface AllRulesProps { @@ -129,6 +131,18 @@ export const AllRules = React.memo( }) => { const [initLoading, setInitLoading] = useState(true); const tableRef = useRef(); + const { + services: { + application: { + capabilities: { actions }, + }, + }, + } = useKibana(); + const [defaultAutoRefreshSetting] = useUiSetting$<{ + on: boolean; + value: number; + idleTimeout: number; + }>(DEFAULT_RULES_TABLE_REFRESH_SETTING); const [ { exportRuleIds, @@ -138,9 +152,16 @@ export const AllRules = React.memo( pagination, rules, selectedRuleIds, + lastUpdated, + showIdleModal, + isRefreshOn, }, dispatch, - ] = useReducer(allRulesReducer(tableRef), initialState); + ] = useReducer(allRulesReducer(tableRef), { + ...initialState, + lastUpdated: Date.now(), + isRefreshOn: defaultAutoRefreshSetting.on, + }); const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); @@ -159,6 +180,26 @@ export const AllRules = React.memo( }); }, []); + const setShowIdleModal = useCallback((show: boolean) => { + dispatch({ + type: 'setShowIdleModal', + show, + }); + }, []); + + const setLastRefreshDate = useCallback(() => { + dispatch({ + type: 'setLastRefreshDate', + }); + }, []); + + const setAutoRefreshOn = useCallback((on: boolean) => { + dispatch({ + type: 'setAutoRefreshOn', + on, + }); + }, []); + const [isLoadingRules, , reFetchRulesData] = useRules({ pagination, filterOptions, @@ -181,34 +222,25 @@ export const AllRules = React.memo( rulesNotInstalled, rulesNotUpdated ); - const { - services: { - application: { - capabilities: { actions }, - }, - }, - } = useKibana(); const hasActionsPrivileges = useMemo(() => (isBoolean(actions.show) ? actions.show : true), [ actions, ]); const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), + (closePopover: () => void): JSX.Element[] => { + return getBatchItems({ + closePopover, + dispatch, + dispatchToaster, + hasMlPermissions, + hasActionsPrivileges, + loadingRuleIds, + selectedRuleIds, + reFetchRules: reFetchRulesData, + rules, + }); + }, [ dispatch, dispatchToaster, @@ -328,6 +360,94 @@ export const AllRules = React.memo( return false; }, [loadingRuleIds, loadingRulesAction]); + const handleRefreshData = useCallback((): void => { + if (reFetchRulesData != null && !isLoadingAnActionOnRule) { + reFetchRulesData(true); + setLastRefreshDate(); + } + }, [reFetchRulesData, isLoadingAnActionOnRule, setLastRefreshDate]); + + const handleResetIdleTimer = useCallback((): void => { + if (isRefreshOn) { + setShowIdleModal(true); + setAutoRefreshOn(false); + } + }, [setShowIdleModal, setAutoRefreshOn, isRefreshOn]); + + const debounceResetIdleTimer = useMemo(() => { + return debounce(defaultAutoRefreshSetting.idleTimeout, handleResetIdleTimer); + }, [handleResetIdleTimer, defaultAutoRefreshSetting.idleTimeout]); + + useEffect(() => { + const interval = setInterval(() => { + if (isRefreshOn) { + handleRefreshData(); + } + }, defaultAutoRefreshSetting.value); + + return () => { + clearInterval(interval); + }; + }, [isRefreshOn, handleRefreshData, defaultAutoRefreshSetting.value]); + + const handleIdleModalContinue = useCallback((): void => { + setShowIdleModal(false); + handleRefreshData(); + setAutoRefreshOn(true); + }, [setShowIdleModal, setAutoRefreshOn, handleRefreshData]); + + const handleAutoRefreshSwitch = useCallback( + (refreshOn: boolean) => { + if (refreshOn) { + handleRefreshData(); + } + setAutoRefreshOn(refreshOn); + }, + [setAutoRefreshOn, handleRefreshData] + ); + + useEffect(() => { + debounceResetIdleTimer(); + + window.addEventListener('mousemove', debounceResetIdleTimer, { passive: true }); + window.addEventListener('keydown', debounceResetIdleTimer); + + return () => { + window.removeEventListener('mousemove', debounceResetIdleTimer); + window.removeEventListener('keydown', debounceResetIdleTimer); + }; + }, [handleResetIdleTimer, debounceResetIdleTimer]); + + const shouldShowRulesTable = useMemo( + (): boolean => showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading, + [initLoading, rulesCustomInstalled, rulesInstalled] + ); + + const shouldShowPrepackagedRulesPrompt = useMemo( + (): boolean => + rulesCustomInstalled != null && + rulesCustomInstalled === 0 && + prePackagedRuleStatus === 'ruleNotInstalled' && + !initLoading, + [initLoading, prePackagedRuleStatus, rulesCustomInstalled] + ); + + const handleGenericDownloaderSuccess = useCallback( + (exportCount) => { + dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }, + [dispatchToaster] + ); + const tabs = useMemo( () => ( @@ -353,27 +473,37 @@ export const AllRules = React.memo( { - dispatch({ type: 'loadingRuleIds', ids: [], actionType: null }); - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), - color: 'success', - iconType: 'check', - }, - }); - }} + onExportSuccess={handleGenericDownloaderSuccess} exportSelectedData={exportRules} /> {tabs} - + <> - + {(isLoadingRules || isLoadingRulesStatuses) && ( + + )} + + } + > ( /> - {(loading || isLoadingRules || isLoadingAnActionOnRule || isLoadingRulesStatuses) && - !initLoading && ( - - )} - {rulesCustomInstalled != null && - rulesCustomInstalled === 0 && - prePackagedRuleStatus === 'ruleNotInstalled' && - !initLoading && ( - - )} + {isLoadingAnActionOnRule && !initLoading && ( + + )} + {shouldShowPrepackagedRulesPrompt && ( + + )} {initLoading && ( )} - {showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !initLoading && ( + {showIdleModal && ( + + +

{i18n.REFRESH_PROMPT_BODY}

+
+
+ )} + {shouldShowRulesTable && ( <> - - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - - - - - {i18n.SELECTED_RULES(selectedRuleIds.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} - reFetchRulesData(true)} - > - {i18n.REFRESH} - - - - + { + let reducer: (state: State, action: Action) => State; + + beforeEach(() => { + jest.useFakeTimers(); + jest + .spyOn(global.Date, 'now') + .mockImplementationOnce(() => new Date('2020-10-31T11:01:58.135Z').valueOf()); + reducer = allRulesReducer({ current: undefined }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('#exportRuleIds', () => { + test('should update state with rules to be exported', () => { + const { loadingRuleIds, loadingRulesAction, exportRuleIds } = reducer(initialState, { + type: 'exportRuleIds', + ids: ['123', '456'], + }); + + expect(loadingRuleIds).toEqual(['123', '456']); + expect(exportRuleIds).toEqual(['123', '456']); + expect(loadingRulesAction).toEqual('export'); + }); + }); + + describe('#loadingRuleIds', () => { + test('should update state with rule ids with a pending action', () => { + const { loadingRuleIds, loadingRulesAction } = reducer(initialState, { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: 'enable', + }); + + expect(loadingRuleIds).toEqual(['123', '456']); + expect(loadingRulesAction).toEqual('enable'); + }); + + test('should update loadingIds to empty array if action is null', () => { + const { loadingRuleIds, loadingRulesAction } = reducer(initialState, { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: null, + }); + + expect(loadingRuleIds).toEqual([]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should append rule ids to any existing loading ids', () => { + const { loadingRuleIds, loadingRulesAction } = reducer( + { ...initialState, loadingRuleIds: ['abc'] }, + { + type: 'loadingRuleIds', + ids: ['123', '456'], + actionType: 'duplicate', + } + ); + + expect(loadingRuleIds).toEqual(['abc', '123', '456']); + expect(loadingRulesAction).toEqual('duplicate'); + }); + }); + + describe('#selectedRuleIds', () => { + test('should update state with selected rule ids', () => { + const { selectedRuleIds } = reducer(initialState, { + type: 'selectedRuleIds', + ids: ['123', '456'], + }); + + expect(selectedRuleIds).toEqual(['123', '456']); + }); + }); + + describe('#setRules', () => { + test('should update rules and reset loading/selected rule ids', () => { + const { selectedRuleIds, loadingRuleIds, loadingRulesAction, pagination, rules } = reducer( + initialState, + { + type: 'setRules', + rules: [mockRule('someRuleId')], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + } + ); + + expect(rules).toEqual([mockRule('someRuleId')]); + expect(selectedRuleIds).toEqual([]); + expect(loadingRuleIds).toEqual([]); + expect(loadingRulesAction).toBeNull(); + expect(pagination).toEqual({ + page: 1, + perPage: 20, + total: 0, + }); + }); + }); + + describe('#updateRules', () => { + test('should return existing and new rules', () => { + const existingRule = { ...mockRule('123'), rule_id: 'rule-123' }; + const { rules, loadingRulesAction } = reducer( + { ...initialState, rules: [existingRule] }, + { + type: 'updateRules', + rules: [mockRule('someRuleId')], + } + ); + + expect(rules).toEqual([existingRule, mockRule('someRuleId')]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should return updated rule', () => { + const updatedRule = { ...mockRule('someRuleId'), description: 'updated rule' }; + const { rules, loadingRulesAction } = reducer( + { ...initialState, rules: [mockRule('someRuleId')] }, + { + type: 'updateRules', + rules: [updatedRule], + } + ); + + expect(rules).toEqual([updatedRule]); + expect(loadingRulesAction).toBeNull(); + }); + + test('should return updated existing loading rule ids', () => { + const existingRule = { ...mockRule('someRuleId'), id: '123', rule_id: 'rule-123' }; + const { loadingRuleIds, loadingRulesAction } = reducer( + { + ...initialState, + rules: [existingRule], + loadingRuleIds: ['123'], + loadingRulesAction: 'enable', + }, + { + type: 'updateRules', + rules: [mockRule('someRuleId')], + } + ); + + expect(loadingRuleIds).toEqual(['123']); + expect(loadingRulesAction).toEqual('enable'); + }); + }); + + describe('#updateFilterOptions', () => { + test('should return existing and new rules', () => { + const paginationMock: PaginationOptions = { + page: 1, + perPage: 20, + total: 0, + }; + const filterMock: FilterOptions = { + filter: 'host.name:*', + sortField: 'enabled', + sortOrder: 'desc', + }; + const { filterOptions, pagination } = reducer(initialState, { + type: 'updateFilterOptions', + filterOptions: filterMock, + pagination: paginationMock, + }); + + expect(filterOptions).toEqual(filterMock); + expect(pagination).toEqual(paginationMock); + }); + }); + + describe('#failure', () => { + test('should reset rules value to empty array', () => { + const { rules } = reducer(initialState, { + type: 'failure', + }); + + expect(rules).toEqual([]); + }); + }); + + describe('#setLastRefreshDate', () => { + test('should update last refresh date with current date', () => { + const { lastUpdated } = reducer(initialState, { + type: 'setLastRefreshDate', + }); + + expect(lastUpdated).toEqual(1604142118135); + }); + }); + + describe('#setShowIdleModal', () => { + test('should hide idle modal and restart refresh if "show" is false', () => { + const { showIdleModal, isRefreshOn } = reducer(initialState, { + type: 'setShowIdleModal', + show: false, + }); + + expect(showIdleModal).toBeFalsy(); + expect(isRefreshOn).toBeTruthy(); + }); + + test('should show idle modal and pause refresh if "show" is true', () => { + const { showIdleModal, isRefreshOn } = reducer(initialState, { + type: 'setShowIdleModal', + show: true, + }); + + expect(showIdleModal).toBeTruthy(); + expect(isRefreshOn).toBeFalsy(); + }); + }); + + describe('#setAutoRefreshOn', () => { + test('should pause auto refresh if "paused" is true', () => { + const { isRefreshOn } = reducer(initialState, { + type: 'setAutoRefreshOn', + on: true, + }); + + expect(isRefreshOn).toBeTruthy(); + }); + + test('should resume auto refresh if "paused" is false', () => { + const { isRefreshOn } = reducer(initialState, { + type: 'setAutoRefreshOn', + on: false, + }); + + expect(isRefreshOn).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts index ff9b41bed06f5e..d603e5791f5ce1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts @@ -20,6 +20,9 @@ export interface State { pagination: PaginationOptions; rules: Rule[]; selectedRuleIds: string[]; + lastUpdated: number; + showIdleModal: boolean; + isRefreshOn: boolean; } export type Action = @@ -33,7 +36,10 @@ export type Action = filterOptions: Partial; pagination: Partial; } - | { type: 'failure' }; + | { type: 'failure' } + | { type: 'setLastRefreshDate' } + | { type: 'setShowIdleModal'; show: boolean } + | { type: 'setAutoRefreshOn'; on: boolean }; export const allRulesReducer = ( tableRef: React.MutableRefObject | undefined> @@ -85,27 +91,24 @@ export const allRulesReducer = ( }; } case 'updateRules': { - if (state.rules != null) { - const ruleIds = state.rules.map((r) => r.id); - const updatedRules = action.rules.reduce((rules, updatedRule) => { - let newRules = rules; - if (ruleIds.includes(updatedRule.id)) { - newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); - } else { - newRules = [...newRules, updatedRule]; - } - return newRules; - }, state.rules); - const updatedRuleIds = action.rules.map((r) => r.id); - const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); - return { - ...state, - rules: updatedRules, - loadingRuleIds: newLoadingRuleIds, - loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, - }; - } - return state; + const ruleIds = state.rules.map((r) => r.id); + const updatedRules = action.rules.reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.id)) { + newRules = newRules.map((r) => (updatedRule.id === r.id ? updatedRule : r)); + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); + const updatedRuleIds = action.rules.map((r) => r.id); + const newLoadingRuleIds = state.loadingRuleIds.filter((id) => !updatedRuleIds.includes(id)); + return { + ...state, + rules: updatedRules, + loadingRuleIds: newLoadingRuleIds, + loadingRulesAction: newLoadingRuleIds.length === 0 ? null : state.loadingRulesAction, + }; } case 'updateFilterOptions': { return { @@ -126,6 +129,25 @@ export const allRulesReducer = ( rules: [], }; } + case 'setLastRefreshDate': { + return { + ...state, + lastUpdated: Date.now(), + }; + } + case 'setShowIdleModal': { + return { + ...state, + showIdleModal: action.show, + isRefreshOn: !action.show, + }; + } + case 'setAutoRefreshOn': { + return { + ...state, + isRefreshOn: action.on, + }; + } default: return state; } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx index 92f69d79110d2b..a8205c24dca65f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -5,16 +5,47 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import { act } from '@testing-library/react'; import { RulesTableFilters } from './rules_table_filters'; describe('RulesTableFilters', () => { - it('renders correctly', () => { - const wrapper = shallow( - - ); + it('renders no numbers next to rule type button filter if none exist', async () => { + await act(async () => { + const wrapper = mount( + + ); - expect(wrapper.find('[data-test-subj="show-elastic-rules-filter-button"]')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( + 'Elastic rules' + ); + expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( + 'Custom rules' + ); + }); + }); + + it('renders number of custom and prepackaged rules', async () => { + await act(async () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( + 'Elastic rules (9)' + ); + expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( + 'Custom rules (10)' + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx index 0f201fcbaa441a..0b83a8437cc1ac 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -14,6 +14,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; + import * as i18n from '../../translations'; import { FilterOptions } from '../../../../../containers/detection_engine/rules'; @@ -76,7 +77,7 @@ const RulesTableFiltersComponent = ({ return ( - + @@ -102,7 +104,7 @@ const RulesTableFiltersComponent = ({ {i18n.ELASTIC_RULES} @@ -111,7 +113,7 @@ const RulesTableFiltersComponent = ({ <> {i18n.CUSTOM_RULES} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx new file mode 100644 index 00000000000000..3d49295bde50a0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { ThemeProvider } from 'styled-components'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { waitFor } from '@testing-library/react'; + +import { AllRulesUtilityBar } from './utility_bar'; + +describe('AllRules', () => { + it('renders AllRulesUtilityBar total rules and selected rules', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="showingRules"]').at(0).text()).toEqual('Showing 4 rules'); + expect(wrapper.find('[data-test-subj="selectedRules"]').at(0).text()).toEqual( + 'Selected 1 rule' + ); + }); + + it('renders utility actions if user has permissions', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="bulkActions"]').exists()).toBeTruthy(); + }); + + it('renders no utility actions if user has no permissions', () => { + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + expect(wrapper.find('[data-test-subj="bulkActions"]').exists()).toBeFalsy(); + }); + + it('invokes refresh on refresh action click', () => { + const mockRefresh = jest.fn(); + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + wrapper.find('[data-test-subj="refreshRulesAction"] button').at(0).simulate('click'); + + expect(mockRefresh).toHaveBeenCalled(); + }); + + it('invokes onRefreshSwitch when auto refresh switch is clicked', async () => { + const mockSwitch = jest.fn(); + const wrapper = mount( + ({ eui: euiDarkVars, darkMode: true })}> + + + ); + + await waitFor(() => { + wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); + wrapper.find('[data-test-subj="refreshSettingsSwitch"] button').first().simulate('click'); + expect(mockSwitch).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx new file mode 100644 index 00000000000000..3553dcc9b7c143 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/utility_bar.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiContextMenuPanel, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../../../../../common/components/utility_bar'; +import * as i18n from '../translations'; + +interface AllRulesUtilityBarProps { + userHasNoPermissions: boolean; + numberSelectedRules: number; + paginationTotal: number; + isAutoRefreshOn: boolean; + onRefresh: (refreshRule: boolean) => void; + onGetBatchItemsPopoverContent: (closePopover: () => void) => JSX.Element[]; + onRefreshSwitch: (checked: boolean) => void; +} + +export const AllRulesUtilityBar = React.memo( + ({ + userHasNoPermissions, + onRefresh, + paginationTotal, + numberSelectedRules, + onGetBatchItemsPopoverContent, + isAutoRefreshOn, + onRefreshSwitch, + }) => { + const handleGetBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [onGetBatchItemsPopoverContent] + ); + + const handleAutoRefreshSwitch = useCallback( + (closePopover: () => void) => (e: EuiSwitchEvent) => { + onRefreshSwitch(e.target.checked); + closePopover(); + }, + [onRefreshSwitch] + ); + + const handleGetRefreshSettingsPopoverContent = useCallback( + (closePopover: () => void) => ( + , + ]} + /> + ), + [isAutoRefreshOn, handleAutoRefreshSwitch] + ); + + return ( + + + + + {i18n.SHOWING_RULES(paginationTotal)} + + + + + + {i18n.SELECTED_RULES(numberSelectedRules)} + + {!userHasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + + {i18n.REFRESH} + + + {i18n.REFRESH_RULE_POPOVER_LABEL} + + + + + ); + } +); + +AllRulesUtilityBar.displayName = 'AllRulesUtilityBar'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index d20b97a98fbf5b..38fb457185b67b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -554,3 +554,38 @@ export const IMPORT_FAILED_DETAILED = (ruleId: string, statusCode: number, messa defaultMessage: 'Rule ID: {ruleId}\n Status Code: {statusCode}\n Message: {message}', } ); + +export const REFRESH_PROMPT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptTitle', + { + defaultMessage: 'Are you still there?', + } +); + +export const REFRESH_PROMPT_CONFIRM = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptConfirm', + { + defaultMessage: 'Continue', + } +); + +export const REFRESH_PROMPT_BODY = i18n.translate( + 'xpack.securitySolution.detectionEngine.components.allRules.refreshPromptBody', + { + defaultMessage: 'Rule auto-refresh has been paused. Click "Continue" to resume.', + } +); + +export const REFRESH_RULE_POPOVER_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.refreshRulePopoverDescription', + { + defaultMessage: 'Automatically refresh table', + } +); + +export const REFRESH_RULE_POPOVER_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.refreshRulePopoverLabel', + { + defaultMessage: 'Refresh settings', + } +); 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 4119127d5a108d..f56d7d90cf2df8 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 @@ -25,10 +25,10 @@ import styled from 'styled-components'; import { LoadingPanel } from '../../loading'; import { OnChangeItemsPerPage, OnChangePage } from '../events'; -import { LastUpdatedAt } from './last_updated'; import * as i18n from './translations'; import { useEventDetailsWidthContext } from '../../../../common/components/events_viewer/event_details_width_context'; import { useManageTimeline } from '../../manage_timeline'; +import { LastUpdatedAt } from '../../../../common/components/last_updated'; export const isCompactFooter = (width: number): boolean => width < 600; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx deleted file mode 100644 index 06ece50690c09c..00000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/last_updated.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; -import React, { useEffect, useState } from 'react'; - -import * as i18n from './translations'; - -interface LastUpdatedAtProps { - compact?: boolean; - updatedAt: number; -} - -export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>( - ({ date, prefix, updatedAt }) => ( - <> - {prefix} - { - - } - - ) -); - -Updated.displayName = 'Updated'; - -const prefix = ` ${i18n.UPDATED} `; - -export const LastUpdatedAt = React.memo(({ compact = false, updatedAt }) => { - const [date, setDate] = useState(Date.now()); - - function tick() { - setDate(Date.now()); - } - - useEffect(() => { - const timerID = setInterval(() => tick(), 10000); - return () => { - clearInterval(timerID); - }; - }, []); - - return ( - - - - } - > - - - {!compact ? : null} - - - ); -}); - -LastUpdatedAt.displayName = 'LastUpdatedAt'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts index f581d0757bc3cf..016406d6bd061b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/translations.ts @@ -36,10 +36,6 @@ export const TOTAL_COUNT_OF_EVENTS = i18n.translate( } ); -export const UPDATED = i18n.translate('xpack.securitySolution.footer.updated', { - defaultMessage: 'Updated', -}); - export const AUTO_REFRESH_ACTIVE = i18n.translate( 'xpack.securitySolution.footer.autoRefreshActiveDescription', { 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 94b820344b37ca..773e84d9c88fce 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 @@ -407,6 +407,8 @@ export const getResult = (): RuleAlertType => ({ note: '# Investigative notes', version: 1, exceptionsList: getListArrayMock(), + concurrentSearches: undefined, + itemsPerSearch: undefined, }, createdAt: new Date('2019-12-13T16:40:33.400Z'), updatedAt: new Date('2019-12-13T16:40:33.400Z'), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json index 7255325358baf6..4e9477f3f2f73b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json @@ -257,6 +257,11 @@ "original_time": { "type": "date" }, + "original_signal": { + "type": "object", + "dynamic": false, + "enabled": false + }, "original_event": { "properties": { "action": { 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 8c7a19869ce188..aa409580df9655 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 @@ -102,6 +102,8 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_mapping: threatMapping, threat_query: threatQuery, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threshold, throttle, timestamp_override: timestampOverride, @@ -193,6 +195,8 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatIndex, threatLanguage, + concurrentSearches, + itemsPerSearch, threshold, timestampOverride, references, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index 6ba7bc78fbded6..97c05b4626ddc2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -85,6 +85,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, to, @@ -182,6 +184,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts index 4d992c6c7029d0..4b75127af1bc76 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts @@ -9,6 +9,7 @@ import { getFindResultStatus, ruleStatusRequest, getResult } from '../__mocks__/ import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { findRulesStatusesRoute } from './find_rules_status_route'; import { RuleStatusResponse } from '../../rules/types'; +import { AlertExecutionStatusErrorReasons } from '../../../../../../alerts/common'; jest.mock('../../signals/rule_status_service'); @@ -57,7 +58,7 @@ describe('find_statuses', () => { status: 'error', lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate, error: { - reason: 'read', + reason: AlertExecutionStatusErrorReasons.Read, message: 'oops', }, }; 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 7cbcf25590921c..688036c59c8ffe 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 @@ -169,6 +169,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threshold, timestamp_override: timestampOverride, to, @@ -235,6 +237,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, @@ -284,6 +288,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, references, note, version, 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 4c310774ec72b2..7dfb4daa1a0a22 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 @@ -97,6 +97,8 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, throttle, references, @@ -162,6 +164,8 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index dbdcd9844c0a79..aadb13ef54e726 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -83,6 +83,8 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, throttle, references, @@ -161,6 +163,8 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, 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 b93b3f319193f4..f4a31c2bb456dd 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 @@ -102,6 +102,8 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, references, @@ -174,6 +176,8 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index ea19fed5d6668b..7ad525b67f7aa1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -86,6 +86,8 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, throttle, timestamp_override: timestampOverride, references, @@ -163,6 +165,8 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, references, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index fb4ba855f65369..7360dc77aac22c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -151,6 +151,8 @@ export const transformAlertToRule = ( threat_query: alert.params.threatQuery, threat_mapping: alert.params.threatMapping, threat_language: alert.params.threatLanguage, + concurrent_searches: alert.params.concurrentSearches, + items_per_search: alert.params.itemsPerSearch, throttle: ruleActions?.ruleThrottle || 'no_actions', timestamp_override: alert.params.timestampOverride, note: alert.params.note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index 25e47b38e8a564..b613061ac85f2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -27,6 +27,7 @@ import { import { responseMock } from './__mocks__'; import { exampleRuleStatus, exampleFindRuleStatusResponse } from '../signals/__mocks__/es_results'; import { getResult } from './__mocks__/request_responses'; +import { AlertExecutionStatusErrorReasons } from '../../../../../alerts/common'; let alertsClient: ReturnType; @@ -464,7 +465,7 @@ describe('utils', () => { status: 'error', lastExecutionDate: foundRule.executionStatus.lastExecutionDate, error: { - reason: 'read', + reason: AlertExecutionStatusErrorReasons.Read, message: 'oops', }, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts index 271b1043ea5683..68199c531a2fec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts @@ -43,6 +43,8 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ threatFilters: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, threatQuery: undefined, threatIndex: undefined, threshold: undefined, @@ -94,6 +96,8 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ threatMapping: undefined, threatQuery: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, threshold: undefined, timestampOverride: undefined, to: 'now', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 776882d0f84941..3c814ce7e66067 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -46,6 +46,8 @@ export const createRules = async ({ threatFilters, threatIndex, threatLanguage, + concurrentSearches, + itemsPerSearch, threatQuery, threatMapping, threshold, @@ -96,6 +98,8 @@ export const createRules = async ({ threatFilters, threatIndex, threatQuery, + concurrentSearches, + itemsPerSearch, threatMapping, threatLanguage, timestampOverride, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 0a43c652234d09..4c01318f02cdea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -51,6 +51,8 @@ export const installPrepackagedRules = ( threat_filters: threatFilters, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, threat_query: threatQuery, threat_index: threatIndex, threshold, @@ -103,6 +105,8 @@ export const installPrepackagedRules = ( threatFilters, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, threatQuery, threatIndex, threshold, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index ef7cd35f28f1bd..60f1d599470e3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -154,6 +154,8 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, timestampOverride: undefined, to: 'now', type: 'query', @@ -203,6 +205,8 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, timestampOverride: undefined, to: 'now', type: 'machine_learning', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 1982dcf9dd9b67..22b2593283696d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -49,6 +49,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -97,6 +99,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -141,6 +145,8 @@ export const patchRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index fb4763a982f43f..f6ab3fb0c3ed2e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -92,6 +92,8 @@ import { ThreatMappingOrUndefined, ThreatFiltersOrUndefined, ThreatLanguageOrUndefined, + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, } from '../../../../common/detection_engine/schemas/types/threat_mapping'; import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; @@ -234,6 +236,8 @@ export interface CreateRulesOptions { threatIndex: ThreatIndexOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; threatLanguage: ThreatLanguageOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: To; @@ -284,6 +288,8 @@ export interface UpdateRulesOptions { threatIndex: ThreatIndexOrUndefined; threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; threatLanguage: ThreatLanguageOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: To; @@ -327,6 +333,8 @@ export interface PatchRulesOptions { severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; threat: ThreatOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; threshold: ThresholdOrUndefined; threatFilters: ThreatFiltersOrUndefined; threatIndex: ThreatIndexOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index c685c4198c1193..3d4b27b74c0af0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -52,6 +52,8 @@ export const updatePrepackagedRules = async ( threat_query: threatQuery, threat_mapping: threatMapping, threat_language: threatLanguage, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, timestamp_override: timestampOverride, references, version, @@ -107,6 +109,8 @@ export const updatePrepackagedRules = async ( threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, references, version, note, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index a33651580ef221..34be0f6ad843dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -49,6 +49,8 @@ export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ threatMapping: undefined, threatLanguage: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], @@ -99,6 +101,8 @@ export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ threatMapping: undefined, threatLanguage: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: 'now', type: 'machine_learning', references: ['http://www.example.com'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 3da921ed47f26e..5168affca5c624 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -50,6 +50,8 @@ export const updateRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, @@ -99,6 +101,8 @@ export const updateRules = async ({ threatQuery, threatMapping, threatLanguage, + concurrentSearches, + itemsPerSearch, timestampOverride, to, type, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 654383ff97c7ad..8555af424ecd79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -60,6 +60,8 @@ describe('utils', () => { threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: undefined, timestampOverride: undefined, type: undefined, @@ -108,6 +110,8 @@ describe('utils', () => { threatQuery: undefined, threatMapping: undefined, threatLanguage: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, to: undefined, timestampOverride: undefined, type: undefined, @@ -158,6 +162,8 @@ describe('utils', () => { threatLanguage: undefined, to: undefined, timestampOverride: undefined, + concurrentSearches: undefined, + itemsPerSearch: undefined, type: undefined, references: undefined, version: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index a9a100543b528a..83d9e3fd3e59f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -43,6 +43,8 @@ import { } from '../../../../common/detection_engine/schemas/common/schemas'; import { PartialFilter } from '../types'; import { + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, ListArrayOrUndefined, ThreatFiltersOrUndefined, ThreatIndexOrUndefined, @@ -98,6 +100,8 @@ export interface UpdateProperties { threatQuery: ThreatQueryOrUndefined; threatMapping: ThreatMappingOrUndefined; threatLanguage: ThreatLanguageOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: ToOrUndefined; type: TypeOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh index 23c1914387c44d..4807afd71e8d2c 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_threat_data.sh @@ -12,7 +12,7 @@ set -e # Adds port mock data to a threat list for testing. # Example: ./create_threat_data.sh -# Example: ./create_threat_data.sh 1000 2000 +# Example: ./create_threat_data.sh 1 500 START=${1:-1} END=${2:-1000} @@ -22,7 +22,7 @@ do { curl -s -k \ -H "Content-Type: application/json" \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X PUT ${ELASTICSEARCH_URL}/mock-threat-list/_doc/$i \ + -X PUT ${ELASTICSEARCH_URL}/mock-threat-list-1/_doc/$i \ --data " { \"@timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json new file mode 100644 index 00000000000000..c573db7fbca359 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_threat_mapping_perf.json @@ -0,0 +1,32 @@ +{ + "concurrent_searches": 10, + "items_per_search": 10, + "index": ["auditbeat-*", "endgame-*", "filebeat-*", "logs-*", "packetbeat-*", "winlogbeat-*"], + "name": "Indicator Match Concurrent Searches", + "description": "Does 100 Concurrent searches with 10 items per search", + "rule_id": "indicator_concurrent_search", + "risk_score": 1, + "severity": "high", + "type": "threat_match", + "query": "*:*", + "tags": ["concurrent_searches_test", "from_script"], + "threat_index": ["mock-threat-list-1"], + "threat_language": "kuery", + "threat_query": "*:*", + "threat_mapping": [ + { + "entries": [ + { + "field": "source.port", + "type": "mapping", + "value": "source.port" + }, + { + "field": "source.ip", + "type": "mapping", + "value": "source.ip" + } + ] + } + ] +} 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 4559a658c9583c..92e6b9562d9706 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 @@ -68,6 +68,8 @@ export const sampleRuleAlertParams = ( threat: undefined, version: 1, exceptionsList: getListArrayMock(), + concurrentSearches: undefined, + itemsPerSearch: undefined, }); export const sampleRuleSO = (): SavedObject => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index 75a7de8cd2c443..ad060a9304e848 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -20,7 +20,7 @@ import { objectPairIntersection, objectArrayIntersection, } from './build_bulk_body'; -import { SignalHit } from './types'; +import { SignalHit, SignalSourceHit } from './types'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; describe('buildBulkBody', () => { @@ -441,6 +441,206 @@ describe('buildBulkBody', () => { }; expect(fakeSignalSourceHit).toEqual(expected); }); + + test('bulk body builds "original_signal" if it exists already as a numeric', () => { + const sampleParams = sampleRuleAlertParams(); + const sampleDoc = sampleDocNoSortId(); + delete sampleDoc._source.source; + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: 123, + }, + } as unknown) as SignalSourceHit; + const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + const expected: Omit & { someKey: string } = { + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_signal: 123, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + parents: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2020-04-20T21:27:45+0000', + status: 'open', + rule: { + actions: [], + author: ['Elastic'], + building_block_type: 'default', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + risk_score_mapping: [], + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + license: 'Elastic License', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + severity_mapping: [], + tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], + type: 'query', + to: 'now', + note: '', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + version: 1, + updated_at: fakeSignalSourceHit.signal.rule?.updated_at, + created_at: fakeSignalSourceHit.signal.rule?.created_at, + throttle: 'no_actions', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, + }; + expect(fakeSignalSourceHit).toEqual(expected); + }); + + test('bulk body builds "original_signal" if it exists already as an object', () => { + const sampleParams = sampleRuleAlertParams(); + const sampleDoc = sampleDocNoSortId(); + delete sampleDoc._source.source; + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { child_1: { child_2: 'nested data' } }, + }, + } as unknown) as SignalSourceHit; + const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + actions: [], + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + const expected: Omit & { someKey: string } = { + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_signal: { child_1: { child_2: 'nested data' } }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + parents: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + ancestors: [ + { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 0, + }, + ], + original_time: '2020-04-20T21:27:45+0000', + status: 'open', + rule: { + actions: [], + author: ['Elastic'], + building_block_type: 'default', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + risk_score_mapping: [], + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + license: 'Elastic License', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + severity_mapping: [], + tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], + type: 'query', + to: 'now', + note: '', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + version: 1, + updated_at: fakeSignalSourceHit.signal.rule?.updated_at, + created_at: fakeSignalSourceHit.signal.rule?.created_at, + throttle: 'no_actions', + exceptions_list: getListArrayMock(), + }, + depth: 1, + }, + }; + expect(fakeSignalSourceHit).toEqual(expected); + }); }); describe('buildSignalFromSequence', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts index cc454ac1e94624..a704d076880bf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts @@ -154,7 +154,7 @@ export const buildSignalFromEvent = ( const rule = applyOverrides ? buildRuleWithOverrides(ruleSO, event._source) : buildRuleWithoutOverrides(ruleSO); - const signal = { + const signal: Signal = { ...buildSignal([event], rule), ...additionalSignalFields(event), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts index 106a049002e058..ada939ed0941a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.test.ts @@ -5,7 +5,8 @@ */ import { sampleDocNoSortId } from './__mocks__/es_results'; -import { buildEventTypeSignal } from './build_event_type_signal'; +import { buildEventTypeSignal, isEventTypeSignal } from './build_event_type_signal'; +import { BaseSignalHit } from './types'; describe('buildEventTypeSignal', () => { beforeEach(() => { @@ -44,4 +45,57 @@ describe('buildEventTypeSignal', () => { }; expect(eventType).toEqual(expected); }); + + test('It validates a sample doc with no signal type as "false"', () => { + const doc = sampleDocNoSortId(); + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates a sample doc with a signal type as "true"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: { + rule: { id: 'id-123' }, + }, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(true); + }); + + test('It validates a numeric signal string as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: 'something', + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates an empty object as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: {}, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); + + test('It validates an empty rule object as "false"', () => { + const doc: BaseSignalHit = ({ + ...sampleDocNoSortId(), + _source: { + ...sampleDocNoSortId()._source, + signal: { + rule: {}, + }, + }, + } as unknown) as BaseSignalHit; + expect(isEventTypeSignal(doc)).toEqual(false); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts index 81c9d1dedcc56c..3d78cf5ce5e467 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_event_type_signal.ts @@ -13,3 +13,16 @@ export const buildEventTypeSignal = (doc: BaseSignalHit): object => { return { kind: 'signal' }; } }; + +/** + * Given a document this will return true if that document is a signal + * document. We can't guarantee the code will call this function with a document + * before adding the _source.event.kind = "signal" from "buildEventTypeSignal" + * so we do basic testing to ensure that if the object has the fields of: + * "signal.rule.id" then it will be one of our signals rather than a customer + * overwritten signal. + * @param doc The document which might be a signal or it might be a regular log + */ +export const isEventTypeSignal = (doc: BaseSignalHit): boolean => { + return doc._source.signal?.rule?.id != null && typeof doc._source.signal?.rule?.id === 'string'; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts index d0c451bbdf2e2b..c5e6bc9f157e01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts @@ -5,8 +5,14 @@ */ import { sampleDocNoSortId } from './__mocks__/es_results'; -import { buildSignal, buildParent, buildAncestors, additionalSignalFields } from './build_signal'; -import { Signal, Ancestor } from './types'; +import { + buildSignal, + buildParent, + buildAncestors, + additionalSignalFields, + removeClashes, +} from './build_signal'; +import { Signal, Ancestor, BaseSignalHit } from './types'; import { getRulesSchemaMock, ANCHOR_DATE, @@ -302,4 +308,64 @@ describe('buildSignal', () => { ]; expect(signal).toEqual(expected); }); + + describe('removeClashes', () => { + test('it will call renameClashes with a regular doc and not mutate it if it does not have a signal clash', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const output = removeClashes(doc); + expect(output).toBe(doc); // reference check + }); + + test('it will call renameClashes with a regular doc and not change anything', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const output = removeClashes(doc); + expect(output).toEqual(doc); // deep equal check + }); + + test('it will remove a "signal" numeric clash', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: 127, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); + }); + + test('it will remove a "signal" object clash', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { child_1: { child_2: 'Test nesting' } }, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); + }); + + test('it will not remove a "signal" if that is signal is one of our signals', () => { + const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + const doc = ({ + ...sampleDoc, + _source: { + ...sampleDoc._source, + signal: { rule: { id: '123' } }, + }, + } as unknown) as BaseSignalHit; + const output = removeClashes(doc); + const expected = { + ...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'), + _source: { + ...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')._source, + signal: { rule: { id: '123' } }, + }, + }; + expect(output).toEqual(expected); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index 947938de6caca6..b36a1cbb4a6b3e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -5,6 +5,7 @@ */ import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; +import { isEventTypeSignal } from './build_event_type_signal'; import { Signal, Ancestor, BaseSignalHit } from './types'; /** @@ -48,15 +49,37 @@ export const buildAncestors = (doc: BaseSignalHit): Ancestor[] => { } }; +/** + * This removes any signal named clashes such as if a source index has + * "signal" but is not a signal object we put onto the object. If this + * is our "signal object" then we don't want to remove it. + * @param doc The source index doc to a signal. + */ +export const removeClashes = (doc: BaseSignalHit): BaseSignalHit => { + const { signal, ...noSignal } = doc._source; + if (signal == null || isEventTypeSignal(doc)) { + return doc; + } else { + return { + ...doc, + _source: { ...noSignal }, + }; + } +}; + /** * Builds the `signal.*` fields that are common across all signals. * @param docs The parent signals/events of the new signal to be built. * @param rule The rule that is generating the new signal. */ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => { - const parents = docs.map(buildParent); + const removedClashes = docs.map(removeClashes); + const parents = removedClashes.map(buildParent); const depth = parents.reduce((acc, parent) => Math.max(parent.depth, acc), 0) + 1; - const ancestors = docs.reduce((acc: Ancestor[], doc) => acc.concat(buildAncestors(doc)), []); + const ancestors = removedClashes.reduce( + (acc: Ancestor[], doc) => acc.concat(buildAncestors(doc)), + [] + ); return { parents, ancestors, @@ -72,9 +95,11 @@ export const buildSignal = (docs: BaseSignalHit[], rule: RulesSchema): Signal => */ export const additionalSignalFields = (doc: BaseSignalHit) => { return { - parent: buildParent(doc), + parent: buildParent(removeClashes(doc)), original_time: doc._source['@timestamp'], original_event: doc._source.event ?? undefined, threshold_count: doc._source.threshold_count ?? undefined, + original_signal: + doc._source.signal != null && !isEventTypeSignal(doc) ? doc._source.signal : undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index cfe71f66395b04..50e740e81830f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -54,6 +54,8 @@ const signalSchema = schema.object({ threatQuery: schema.maybe(schema.string()), threatMapping: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threatLanguage: schema.maybe(schema.string()), + concurrentSearches: schema.maybe(schema.number()), + itemsPerSearch: schema.maybe(schema.number()), }); /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 415abc9d995fba..dc68e3949eb363 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -504,7 +504,7 @@ describe('rules_notification_alert_type', () => { await alert.executor(payload); expect(logger.error).toHaveBeenCalled(); expect(logger.error.mock.calls[0][0]).toContain( - 'An error occurred during rule execution: message: "Threat Match rule is missing threatQuery and/or threatIndex and/or threatMapping: threatQuery: "undefined" threatIndex: "undefined" threatMapping: "undefined"" name: "Detect Root/Admin Users" id: "04128c15-0d1b-4716-a4c5-46997ac7f3bd" rule id: "rule-1" signals index: ".siem-signals"' + 'An error occurred during rule execution: message: "Indicator match is missing threatQuery and/or threatIndex and/or threatMapping: threatQuery: "undefined" threatIndex: "undefined" threatMapping: "undefined"" name: "Detect Root/Admin Users" id: "04128c15-0d1b-4716-a4c5-46997ac7f3bd" rule id: "rule-1" signals index: ".siem-signals"' ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index a0d5c833b208cb..1d2b1c23f868f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -119,6 +119,8 @@ export const signalRulesAlertType = ({ timestampOverride, type, exceptionsList, + concurrentSearches, + itemsPerSearch, } = params; const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); @@ -360,7 +362,7 @@ export const signalRulesAlertType = ({ ) { throw new Error( [ - 'Threat Match rule is missing threatQuery and/or threatIndex and/or threatMapping:', + 'Indicator match is missing threatQuery and/or threatIndex and/or threatMapping:', `threatQuery: "${threatQuery}"`, `threatIndex: "${threatIndex}"`, `threatMapping: "${threatMapping}"`, @@ -403,6 +405,8 @@ export const signalRulesAlertType = ({ threatLanguage, buildRuleMessage, threatIndex, + concurrentSearches: concurrentSearches ?? 1, + itemsPerSearch: itemsPerSearch ?? 9000, }); } else if (type === 'query' || type === 'saved_query') { const inputIndex = await getInputIndex(services, version, index); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts index b7cc13fd13a01b..eeeda6561892d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -330,6 +330,18 @@ describe('singleBulkCreate', () => { ]); }); + test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own numeric signal type', () => { + const doc = { ...sampleDocWithAncestors(), _source: { signal: 1234 } }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules does not attempt filters when the signal is not an event type of signal but rather a "clash" from the source index having its own object signal type', () => { + const doc = { ...sampleDocWithAncestors(), _source: { signal: {} } }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', doc); + expect(filtered).toEqual([]); + }); + test('create successful and returns proper createdItemsCount', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockResolvedValue(sampleBulkCreateDuplicateResult); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts index 759890cc9d0746..d8889dcfcf4714 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_bulk_create.ts @@ -14,6 +14,7 @@ import { generateId, makeFloatString, errorAggregator } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { BuildRuleMessage } from './rule_messages'; import { Logger } from '../../../../../../../src/core/server'; +import { isEventTypeSignal } from './build_event_type_signal'; interface SingleBulkCreateParams { filteredEvents: SignalSearchResponse; @@ -50,7 +51,7 @@ export const filterDuplicateRules = ( signalSearchResponse: SignalSearchResponse ) => { return signalSearchResponse.hits.hits.filter((doc) => { - if (doc._source.signal == null) { + if (doc._source.signal == null || !isEventTypeSignal(doc)) { return true; } else { return !( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts index 85d172b3631a93..8eed838fc9680b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.test.ts @@ -19,28 +19,32 @@ import { } from './build_threat_mapping_filter'; import { getThreatMappingMock, - getThreatListSearchResponseMock, getThreatListItemMock, getThreatMappingFilterMock, getFilterThreatMapping, getThreatMappingFiltersShouldMock, getThreatMappingFilterShouldMock, + getThreatListSearchResponseMock, } from './build_threat_mapping_filter.mock'; -import { BooleanFilter } from './types'; +import { BooleanFilter, ThreatListItem } from './types'; describe('build_threat_mapping_filter', () => { describe('buildThreatMappingFilter', () => { test('it should throw if given a chunk over 1024 in size', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; expect(() => - buildThreatMappingFilter({ threatMapping, threatList, chunkSize: 1025 }) + buildThreatMappingFilter({ + threatMapping, + threatList, + chunkSize: 1025, + }) ).toThrow('chunk sizes cannot exceed 1024 in size'); }); test('it should NOT throw if given a chunk under 1024 in size', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; expect(() => buildThreatMappingFilter({ threatMapping, threatList, chunkSize: 1023 }) ).not.toThrow(); @@ -48,30 +52,30 @@ describe('build_threat_mapping_filter', () => { test('it should create the correct entries when using the default mocks', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const filter = buildThreatMappingFilter({ threatMapping, threatList }); expect(filter).toEqual(getThreatMappingFilterMock()); }); test('it should not mutate the original threatMapping', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; buildThreatMappingFilter({ threatMapping, threatList }); expect(threatMapping).toEqual(getThreatMappingMock()); }); test('it should not mutate the original threatListItem', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; buildThreatMappingFilter({ threatMapping, threatList }); - expect(threatList).toEqual(getThreatListSearchResponseMock()); + expect(threatList).toEqual(getThreatListSearchResponseMock().hits.hits); }); }); describe('filterThreatMapping', () => { test('it should not remove any entries when using the default mocks', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const item = filterThreatMapping({ threatMapping, threatListItem }); const expected = getFilterThreatMapping(); @@ -80,7 +84,7 @@ describe('build_threat_mapping_filter', () => { test('it should only give one filtered element if only 1 element is defined', () => { const [firstElement] = getThreatMappingMock(); // get only the first element - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const item = filterThreatMapping({ threatMapping: [firstElement], threatListItem }); const [firstElementFilter] = getFilterThreatMapping(); // get only the first element to compare @@ -89,7 +93,7 @@ describe('build_threat_mapping_filter', () => { test('it should not mutate the original threatMapping', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; filterThreatMapping({ threatMapping, @@ -100,13 +104,13 @@ describe('build_threat_mapping_filter', () => { test('it should not mutate the original threatListItem', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; filterThreatMapping({ threatMapping, threatListItem, }); - expect(threatListItem).toEqual(getThreatListItemMock()); + expect(threatListItem).toEqual(getThreatListSearchResponseMock().hits.hits[0]); }); test('it should remove the entire "AND" clause if one of the pieces of data is missing from the list', () => { @@ -166,9 +170,11 @@ describe('build_threat_mapping_filter', () => { }, ], threatListItem: { - '@timestamp': '2020-09-09T21:59:13Z', - host: { - name: 'host-1', + _source: { + '@timestamp': '2020-09-09T21:59:13Z', + host: { + name: 'host-1', + }, }, }, }); @@ -189,7 +195,7 @@ describe('build_threat_mapping_filter', () => { describe('createInnerAndClauses', () => { test('it should return two clauses given a single entry', () => { const [{ entries: threatMappingEntries }] = getThreatMappingMock(); // get the first element - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -219,7 +225,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -248,7 +254,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); const { bool: { @@ -275,7 +281,7 @@ describe('build_threat_mapping_filter', () => { type: 'mapping', }, ]; - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createInnerAndClauses({ threatMappingEntries, threatListItem }); expect(innerClause).toEqual([]); }); @@ -284,27 +290,31 @@ describe('build_threat_mapping_filter', () => { describe('createAndOrClauses', () => { test('it should return all clauses given the entries', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createAndOrClauses({ threatMapping, threatListItem }); expect(innerClause).toEqual(getThreatMappingFilterShouldMock()); }); test('it should filter out data from entries that do not have mappings', () => { const threatMapping = getThreatMappingMock(); - const threatListItem = { ...getThreatListItemMock(), foo: 'bar' }; + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; + threatListItem._source = { + ...getThreatListSearchResponseMock().hits.hits[0]._source, + foo: 'bar', + }; const innerClause = createAndOrClauses({ threatMapping, threatListItem }); expect(innerClause).toEqual(getThreatMappingFilterShouldMock()); }); test('it should return an empty boolean given an empty array', () => { - const threatListItem = getThreatListItemMock(); + const threatListItem = getThreatListSearchResponseMock().hits.hits[0]; const innerClause = createAndOrClauses({ threatMapping: [], threatListItem }); expect(innerClause).toEqual({ bool: { minimum_should_match: 1, should: [] } }); }); test('it should return an empty boolean clause given an empty object for a threat list item', () => { const threatMapping = getThreatMappingMock(); - const innerClause = createAndOrClauses({ threatMapping, threatListItem: {} }); + const innerClause = createAndOrClauses({ threatMapping, threatListItem: { _source: {} } }); expect(innerClause).toEqual({ bool: { minimum_should_match: 1, should: [] } }); }); }); @@ -312,7 +322,7 @@ describe('build_threat_mapping_filter', () => { describe('buildEntriesMappingFilter', () => { test('it should return all clauses given the entries', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, @@ -326,8 +336,7 @@ describe('build_threat_mapping_filter', () => { test('it should return empty "should" given an empty threat list', () => { const threatMapping = getThreatMappingMock(); - const threatList = getThreatListSearchResponseMock(); - threatList.hits.hits = []; + const threatList: ThreatListItem[] = []; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, @@ -340,7 +349,7 @@ describe('build_threat_mapping_filter', () => { }); test('it should return empty "should" given an empty threat mapping', () => { - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping: [], threatList, @@ -374,7 +383,7 @@ describe('build_threat_mapping_filter', () => { }, ], ]; - const threatList = getThreatListSearchResponseMock(); + const threatList = getThreatListSearchResponseMock().hits.hits; const mapping = buildEntriesMappingFilter({ threatMapping, threatList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts index 346f156a9ec338..294d97e0bf2f12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/build_threat_mapping_filter.ts @@ -53,9 +53,9 @@ export const filterThreatMapping = ({ }: FilterThreatMappingOptions): ThreatMapping => threatMapping .map((threatMap) => { - const atLeastOneItemMissingInThreatList = threatMap.entries.some( - (entry) => get(entry.value, threatListItem) == null - ); + const atLeastOneItemMissingInThreatList = threatMap.entries.some((entry) => { + return get(entry.value, threatListItem._source) == null; + }); if (atLeastOneItemMissingInThreatList) { return { ...threatMap, entries: [] }; } else { @@ -69,7 +69,7 @@ export const createInnerAndClauses = ({ threatListItem, }: CreateInnerAndClausesOptions): BooleanFilter[] => { return threatMappingEntries.reduce((accum, threatMappingEntry) => { - const value = get(threatMappingEntry.value, threatListItem); + const value = get(threatMappingEntry.value, threatListItem._source); if (value != null) { // These values could be potentially 10k+ large so mutating the array intentionally accum.push({ @@ -114,24 +114,21 @@ export const buildEntriesMappingFilter = ({ threatList, chunkSize, }: BuildEntriesMappingFilterOptions): BooleanFilter => { - const combinedShould = threatList.hits.hits.reduce( - (accum, threatListSearchItem) => { - const filteredEntries = filterThreatMapping({ - threatMapping, - threatListItem: threatListSearchItem._source, - }); - const queryWithAndOrClause = createAndOrClauses({ - threatMapping: filteredEntries, - threatListItem: threatListSearchItem._source, - }); - if (queryWithAndOrClause.bool.should.length !== 0) { - // These values can be 10k+ large, so using a push here for performance - accum.push(queryWithAndOrClause); - } - return accum; - }, - [] - ); + const combinedShould = threatList.reduce((accum, threatListSearchItem) => { + const filteredEntries = filterThreatMapping({ + threatMapping, + threatListItem: threatListSearchItem, + }); + const queryWithAndOrClause = createAndOrClauses({ + threatMapping: filteredEntries, + threatListItem: threatListSearchItem, + }); + if (queryWithAndOrClause.bool.should.length !== 0) { + // These values can be 10k+ large, so using a push here for performance + accum.push(queryWithAndOrClause); + } + return accum; + }, []); const should = splitShouldClauses({ should: combinedShould, chunkSize }); return { bool: { should, minimum_should_match: 1 } }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index 037f91240edfaa..43fb759d076203 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getThreatList } from './get_threat_list'; import { buildThreatMappingFilter } from './build_threat_mapping_filter'; import { getFilter } from '../get_filter'; import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; -import { CreateThreatSignalOptions, ThreatSignalResults } from './types'; -import { combineResults } from './utils'; +import { CreateThreatSignalOptions } from './types'; +import { SearchAfterAndBulkCreateReturnType } from '../types'; export const createThreatSignal = async ({ threatMapping, @@ -41,28 +40,11 @@ export const createThreatSignal = async ({ refresh, tags, throttle, - threatFilters, - threatQuery, - threatLanguage, buildRuleMessage, - threatIndex, name, currentThreatList, currentResult, -}: CreateThreatSignalOptions): Promise => { - const threatList = await getThreatList({ - callCluster: services.callCluster, - exceptionItems, - query: threatQuery, - language: threatLanguage, - threatFilters, - index: threatIndex, - searchAfter: currentThreatList.hits.hits[currentThreatList.hits.hits.length - 1].sort, - sortField: undefined, - sortOrder: undefined, - listClient, - }); - +}: CreateThreatSignalOptions): Promise => { const threatFilter = buildThreatMappingFilter({ threatMapping, threatList: currentThreatList, @@ -71,7 +53,12 @@ export const createThreatSignal = async ({ if (threatFilter.query.bool.should.length === 0) { // empty threat list and we do not want to return everything as being // a hit so opt to return the existing result. - return { threatList, results: currentResult }; + logger.debug( + buildRuleMessage( + 'Indicator items are empty after filtering for missing data, returning without attempting a match' + ) + ); + return currentResult; } else { const esFilter = await getFilter({ type, @@ -83,7 +70,13 @@ export const createThreatSignal = async ({ index: inputIndex, lists: exceptionItems, }); - const newResult = await searchAfterAndBulkCreate({ + + logger.debug( + buildRuleMessage( + `${threatFilter.query.bool.should.length} indicator items are being checked for existence of matches` + ) + ); + const result = await searchAfterAndBulkCreate({ gap, previousStartedAt, listClient, @@ -110,7 +103,15 @@ export const createThreatSignal = async ({ throttle, buildRuleMessage, }); - const results = combineResults(currentResult, newResult); - return { threatList, results }; + logger.debug( + buildRuleMessage( + `${ + threatFilter.query.bool.should.length + } items have completed match checks and the total times to search were ${ + result.searchAfterTimes.length !== 0 ? result.searchAfterTimes : '(unknown) ' + }ms` + ) + ); + return result; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts index 8be76dc8caf0f9..e90c45d40de950 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signals.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getThreatList } from './get_threat_list'; +import chunk from 'lodash/fp/chunk'; +import { getThreatList, getThreatListCount } from './get_threat_list'; import { CreateThreatSignalsOptions } from './types'; import { createThreatSignal } from './create_threat_signal'; import { SearchAfterAndBulkCreateReturnType } from '../types'; +import { combineConcurrentResults } from './utils'; export const createThreatSignals = async ({ threatMapping, @@ -45,7 +47,12 @@ export const createThreatSignals = async ({ buildRuleMessage, threatIndex, name, + concurrentSearches, + itemsPerSearch, }: CreateThreatSignalsOptions): Promise => { + logger.debug(buildRuleMessage('Indicator matching rule starting')); + const perPage = concurrentSearches * itemsPerSearch; + let results: SearchAfterAndBulkCreateReturnType = { success: true, bulkCreateTimes: [], @@ -55,6 +62,16 @@ export const createThreatSignals = async ({ errors: [], }; + let threatListCount = await getThreatListCount({ + callCluster: services.callCluster, + exceptionItems, + threatFilters, + query: threatQuery, + language: threatLanguage, + index: threatIndex, + }); + logger.debug(buildRuleMessage(`Total indicator items: ${threatListCount}`)); + let threatList = await getThreatList({ callCluster: services.callCluster, exceptionItems, @@ -66,47 +83,89 @@ export const createThreatSignals = async ({ searchAfter: undefined, sortField: undefined, sortOrder: undefined, + logger, + buildRuleMessage, + perPage, }); - while (threatList.hits.hits.length !== 0 && results.createdSignalsCount <= params.maxSignals) { - ({ threatList, results } = await createThreatSignal({ - threatMapping, - query, - inputIndex, - type, - filters, - language, - savedId, - services, + while (threatList.hits.hits.length !== 0) { + const chunks = chunk(itemsPerSearch, threatList.hits.hits); + logger.debug(buildRuleMessage(`${chunks.length} concurrent indicator searches are starting.`)); + const concurrentSearchesPerformed = chunks.map>( + (slicedChunk) => + createThreatSignal({ + threatMapping, + query, + inputIndex, + type, + filters, + language, + savedId, + services, + exceptionItems, + gap, + previousStartedAt, + listClient, + logger, + eventsTelemetry, + alertId, + outputIndex, + params, + searchAfterSize, + actions, + createdBy, + createdAt, + updatedBy, + updatedAt, + interval, + enabled, + tags, + refresh, + throttle, + buildRuleMessage, + name, + currentThreatList: slicedChunk, + currentResult: results, + }) + ); + const searchesPerformed = await Promise.all(concurrentSearchesPerformed); + results = combineConcurrentResults(results, searchesPerformed); + threatListCount -= threatList.hits.hits.length; + logger.debug( + buildRuleMessage( + `Concurrent indicator match searches completed with ${results.createdSignalsCount} signals found`, + `search times of ${results.searchAfterTimes}ms,`, + `bulk create times ${results.bulkCreateTimes}ms,`, + `all successes are ${results.success}` + ) + ); + if (results.createdSignalsCount >= params.maxSignals) { + logger.debug( + buildRuleMessage( + `Indicator match has reached its max signals count ${params.maxSignals}. Additional indicator items not checked are ${threatListCount}` + ) + ); + break; + } + logger.debug(buildRuleMessage(`Indicator items left to check are ${threatListCount}`)); + + threatList = await getThreatList({ + callCluster: services.callCluster, exceptionItems, - gap, - previousStartedAt, - listClient, - logger, - eventsTelemetry, - alertId, - outputIndex, - params, - searchAfterSize, - actions, - createdBy, - createdAt, - updatedBy, - updatedAt, - interval, - enabled, - tags, - refresh, - throttle, + query: threatQuery, + language: threatLanguage, threatFilters, - threatQuery, + index: threatIndex, + searchAfter: threatList.hits.hits[threatList.hits.hits.length - 1].sort, + sortField: undefined, + sortOrder: undefined, + listClient, buildRuleMessage, - threatIndex, - threatLanguage, - name, - currentThreatList: threatList, - currentResult: results, - })); + logger, + perPage, + }); } + + logger.debug(buildRuleMessage('Indicator matching rule has completed')); return results; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts index 3147eb1705168a..aba3f6f69d706f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/get_threat_list.ts @@ -10,6 +10,7 @@ import { GetSortWithTieBreakerOptions, GetThreatListOptions, SortWithTieBreaker, + ThreatListCountOptions, ThreatListItem, } from './types'; @@ -30,6 +31,8 @@ export const getThreatList = async ({ exceptionItems, threatFilters, listClient, + buildRuleMessage, + logger, }: GetThreatListOptions): Promise> => { const calculatedPerPage = perPage ?? MAX_PER_PAGE; if (calculatedPerPage > 10000) { @@ -43,6 +46,11 @@ export const getThreatList = async ({ exceptionItems ); + logger.debug( + buildRuleMessage( + `Querying the indicator items from the index: "${index}" with searchAfter: "${searchAfter}" for up to ${calculatedPerPage} indicator items` + ) + ); const response: SearchResponse = await callCluster('search', { body: { query: queryFilter, @@ -58,6 +66,8 @@ export const getThreatList = async ({ index, size: calculatedPerPage, }); + + logger.debug(buildRuleMessage(`Retrieved indicator items of size: ${response.hits.hits.length}`)); return response; }; @@ -89,3 +99,30 @@ export const getSortWithTieBreaker = ({ } } }; + +export const getThreatListCount = async ({ + callCluster, + query, + language, + threatFilters, + index, + exceptionItems, +}: ThreatListCountOptions): Promise => { + const queryFilter = getQueryFilter( + query, + language ?? 'kuery', + threatFilters, + index, + exceptionItems + ); + const response: { + count: number; + } = await callCluster('count', { + body: { + query: queryFilter, + }, + ignoreUnavailable: true, + index, + }); + return response.count; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index 0078cf1b3c64f8..2e32a4e682403f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -5,7 +5,6 @@ */ import { Duration } from 'moment'; -import { SearchResponse } from 'elasticsearch'; import { ListClient } from '../../../../../../lists/server'; import { Type, @@ -17,6 +16,8 @@ import { ThreatMappingEntries, ThreatIndex, ThreatLanguageOrUndefined, + ConcurrentSearches, + ItemsPerSearch, } from '../../../../../common/detection_engine/schemas/types/threat_mapping'; import { PartialFilter, RuleTypeParams } from '../../types'; import { AlertServices } from '../../../../../../alerts/server'; @@ -62,6 +63,8 @@ export interface CreateThreatSignalsOptions { threatIndex: ThreatIndex; threatLanguage: ThreatLanguageOrUndefined; name: string; + concurrentSearches: ConcurrentSearches; + itemsPerSearch: ItemsPerSearch; } export interface CreateThreatSignalOptions { @@ -93,24 +96,15 @@ export interface CreateThreatSignalOptions { tags: string[]; refresh: false | 'wait_for'; throttle: string; - threatFilters: PartialFilter[]; - threatQuery: ThreatQuery; buildRuleMessage: BuildRuleMessage; - threatIndex: ThreatIndex; - threatLanguage: ThreatLanguageOrUndefined; name: string; - currentThreatList: SearchResponse; + currentThreatList: ThreatListItem[]; currentResult: SearchAfterAndBulkCreateReturnType; } -export interface ThreatSignalResults { - threatList: SearchResponse; - results: SearchAfterAndBulkCreateReturnType; -} - export interface BuildThreatMappingFilterOptions { threatMapping: ThreatMapping; - threatList: SearchResponse; + threatList: ThreatListItem[]; chunkSize?: number; } @@ -131,7 +125,7 @@ export interface CreateAndOrClausesOptions { export interface BuildEntriesMappingFilterOptions { threatMapping: ThreatMapping; - threatList: SearchResponse; + threatList: ThreatListItem[]; chunkSize: number; } @@ -156,6 +150,17 @@ export interface GetThreatListOptions { threatFilters: PartialFilter[]; exceptionItems: ExceptionListItemSchema[]; listClient: ListClient; + buildRuleMessage: BuildRuleMessage; + logger: Logger; +} + +export interface ThreatListCountOptions { + callCluster: ILegacyScopedClusterClient['callAsCurrentUser']; + query: string; + language: ThreatLanguageOrUndefined; + threatFilters: PartialFilter[]; + index: string[]; + exceptionItems: ExceptionListItemSchema[]; } export interface GetSortWithTieBreakerOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index 27593b40b0c8f6..840d64381c7932 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -6,7 +6,13 @@ import { SearchAfterAndBulkCreateReturnType } from '../types'; -import { calculateAdditiveMax, combineResults } from './utils'; +import { + calculateAdditiveMax, + calculateMax, + calculateMaxLookBack, + combineConcurrentResults, + combineResults, +} from './utils'; describe('utils', () => { describe('calculateAdditiveMax', () => { @@ -156,4 +162,383 @@ describe('utils', () => { ); }); }); + + describe('calculateMax', () => { + test('it should return 0 for two empty arrays', () => { + const max = calculateMax([], []); + expect(max).toEqual('0'); + }); + + test('it should return 5 for two arrays with the numbers 5', () => { + const max = calculateMax(['5'], ['5']); + expect(max).toEqual('5'); + }); + + test('it should return 5 for two arrays with second array having just 5', () => { + const max = calculateMax([], ['5']); + expect(max).toEqual('5'); + }); + + test('it should return 5 for two arrays with first array having just 5', () => { + const max = calculateMax(['5'], []); + expect(max).toEqual('5'); + }); + + test('it should return 10 for the max of the two arrays when the max of each array is 10', () => { + const max = calculateMax(['3', '5', '1'], ['3', '5', '10']); + expect(max).toEqual('10'); + }); + + test('it should return 10 for the max of the two arrays when the max of the first is 10', () => { + const max = calculateMax(['3', '5', '10'], ['3', '5', '1']); + expect(max).toEqual('10'); + }); + }); + + describe('calculateMaxLookBack', () => { + test('it should return null if both are null', () => { + const max = calculateMaxLookBack(null, null); + expect(max).toEqual(null); + }); + + test('it should return undefined if both are undefined', () => { + const max = calculateMaxLookBack(undefined, undefined); + expect(max).toEqual(undefined); + }); + + test('it should return null if both one is null and other other is undefined', () => { + const max = calculateMaxLookBack(undefined, null); + expect(max).toEqual(null); + }); + + test('it should return null if both one is null and other other is undefined with flipped arguments', () => { + const max = calculateMaxLookBack(null, undefined); + expect(max).toEqual(null); + }); + + test('it should return a date time if one argument is null', () => { + const max = calculateMaxLookBack(null, new Date('2020-09-16T03:34:32.390Z')); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is null with flipped arguments', () => { + const max = calculateMaxLookBack(new Date('2020-09-16T03:34:32.390Z'), null); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is undefined', () => { + const max = calculateMaxLookBack(new Date('2020-09-16T03:34:32.390Z'), undefined); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time if one argument is undefined with flipped arguments', () => { + const max = calculateMaxLookBack(undefined, new Date('2020-09-16T03:34:32.390Z')); + expect(max).toEqual(new Date('2020-09-16T03:34:32.390Z')); + }); + + test('it should return a date time that is larger than the other', () => { + const max = calculateMaxLookBack( + new Date('2020-10-16T03:34:32.390Z'), + new Date('2020-09-16T03:34:32.390Z') + ); + expect(max).toEqual(new Date('2020-10-16T03:34:32.390Z')); + }); + + test('it should return a date time that is larger than the other with arguments flipped', () => { + const max = calculateMaxLookBack( + new Date('2020-09-16T03:34:32.390Z'), + new Date('2020-10-16T03:34:32.390Z') + ); + expect(max).toEqual(new Date('2020-10-16T03:34:32.390Z')); + }); + }); + + describe('combineConcurrentResults', () => { + test('it should use the maximum found if given an empty array for newResults', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes + bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, []); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should work with empty arrays for searchAfterTimes and bulkCreateTimes', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: undefined, + createdSignalsCount: 0, + errors: [], + }; + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['30'], // max value from existingResult.searchAfterTimes + bulkCreateTimes: ['25'], // max value from existingResult.bulkCreateTimes + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should get the max of two new results and then combine the result with an existingResult correctly', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should get the max of two new results and then combine the result with an existingResult correctly when the results are flipped around', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T04:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult2, newResult1]); // two array elements are flipped + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should return the max date correctly if one date contains a null', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], // max is 30 + bulkCreateTimes: ['5', '15', '25'], // max is 25 + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const newResult1: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 5, + errors: [], + }; + const newResult2: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['40', '5', '15'], + bulkCreateTimes: ['50', '5', '15'], + lastLookBackDate: null, + createdSignalsCount: 8, + errors: [], + }; + + const expectedResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['70'], // max value between newResult1 and newResult2 + max array value of existingResult (40 + 30 = 70) + bulkCreateTimes: ['75'], // max value between newResult1 and newResult2 + max array value of existingResult (50 + 25 = 75) + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), // max lastLookBackDate + createdSignalsCount: 16, // all the signals counted together (8 + 5 + 3) + errors: [], + }; + + const combinedResults = combineConcurrentResults(existingResult, [newResult1, newResult2]); + expect(combinedResults).toEqual(expectedResult); + }); + + test('it should combine two results with success set to "true" if both are "true"', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.success).toEqual(true); + }); + + test('it should combine two results with success set to "false" if one of them is "false"', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.success).toEqual(false); + }); + + test('it should use the latest date if it is set in the new result', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults.lastLookBackDate?.toISOString()).toEqual('2020-09-16T03:34:32.390Z'); + }); + + test('it should combine the searchAfterTimes and the bulkCreateTimes', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: [], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: [], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual( + expect.objectContaining({ + searchAfterTimes: ['60'], + bulkCreateTimes: ['50'], + }) + ); + }); + + test('it should combine errors together without duplicates', () => { + const existingResult: SearchAfterAndBulkCreateReturnType = { + success: false, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: undefined, + createdSignalsCount: 3, + errors: ['error 1', 'error 2', 'error 3'], + }; + + const newResult: SearchAfterAndBulkCreateReturnType = { + success: true, + searchAfterTimes: ['10', '20', '30'], + bulkCreateTimes: ['5', '15', '25'], + lastLookBackDate: new Date('2020-09-16T03:34:32.390Z'), + createdSignalsCount: 3, + errors: ['error 4', 'error 1', 'error 3', 'error 5'], + }; + const combinedResults = combineConcurrentResults(existingResult, [newResult]); + expect(combinedResults).toEqual( + expect.objectContaining({ + errors: ['error 1', 'error 2', 'error 3', 'error 4', 'error 5'], + }) + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts index 401a4a1acb0652..d6c91fad6d9cb0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.ts @@ -19,6 +19,41 @@ export const calculateAdditiveMax = (existingTimers: string[], newTimers: string return [String(numericNewTimerMax + numericExistingTimerMax)]; }; +/** + * Given two timers this will take the max of each and then get the max from each. + * Max(Max(timer_array_1), Max(timer_array_2)) + * @param existingTimers String array of existing timers + * @param newTimers String array of new timers. + * @returns String array of the new maximum between the two timers + */ +export const calculateMax = (existingTimers: string[], newTimers: string[]): string => { + const numericNewTimerMax = Math.max(0, ...newTimers.map((time) => +time)); + const numericExistingTimerMax = Math.max(0, ...existingTimers.map((time) => +time)); + return String(Math.max(numericNewTimerMax, numericExistingTimerMax)); +}; + +/** + * Given two dates this will return the larger of the two unless one of them is null + * or undefined. If both one or the other is null/undefined it will return the newDate. + * If there is a mix of "undefined" and "null", this will prefer to set it to "null" as having + * a higher value than "undefined" + * @param existingDate The existing date which can be undefined or null or a date + * @param newDate The new date which can be undefined or null or a date + */ +export const calculateMaxLookBack = ( + existingDate: Date | null | undefined, + newDate: Date | null | undefined +): Date | null | undefined => { + const newDateValue = newDate === null ? 1 : newDate === undefined ? 0 : newDate.valueOf(); + const existingDateValue = + existingDate === null ? 1 : existingDate === undefined ? 0 : existingDate.valueOf(); + if (newDateValue >= existingDateValue) { + return newDate; + } else { + return existingDate; + } +}; + /** * Combines two results together and returns the results combined * @param currentResult The current result to combine with a newResult @@ -38,3 +73,39 @@ export const combineResults = ( createdSignalsCount: currentResult.createdSignalsCount + newResult.createdSignalsCount, errors: [...new Set([...currentResult.errors, ...newResult.errors])], }); + +/** + * Combines two results together and returns the results combined + * @param currentResult The current result to combine with a newResult + * @param newResult The new result to combine + */ +export const combineConcurrentResults = ( + currentResult: SearchAfterAndBulkCreateReturnType, + newResult: SearchAfterAndBulkCreateReturnType[] +): SearchAfterAndBulkCreateReturnType => { + const maxedNewResult = newResult.reduce( + (accum, item) => { + const maxSearchAfterTime = calculateMax(accum.searchAfterTimes, item.searchAfterTimes); + const maxBulkCreateTimes = calculateMax(accum.bulkCreateTimes, item.bulkCreateTimes); + const lastLookBackDate = calculateMaxLookBack(accum.lastLookBackDate, item.lastLookBackDate); + return { + success: accum.success && item.success, + searchAfterTimes: [maxSearchAfterTime], + bulkCreateTimes: [maxBulkCreateTimes], + lastLookBackDate, + createdSignalsCount: accum.createdSignalsCount + item.createdSignalsCount, + errors: [...new Set([...accum.errors, ...item.errors])], + }; + }, + { + success: true, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: undefined, + createdSignalsCount: 0, + errors: [], + } + ); + + return combineResults(currentResult, maxedNewResult); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 9d4e7d8a810515..7128feb80ab3c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -157,6 +157,7 @@ export interface Signal { original_event?: SearchTypes; status: Status; threshold_count?: SearchTypes; + original_signal?: SearchTypes; depth: number; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index cf4d989c1f4c84..5cac76e2b0c014 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -44,6 +44,8 @@ import { ThreatQueryOrUndefined, ThreatMappingOrUndefined, ThreatLanguageOrUndefined, + ConcurrentSearchesOrUndefined, + ItemsPerSearchOrUndefined, } from '../../../common/detection_engine/schemas/types/threat_mapping'; import { LegacyCallAPIOptions } from '../../../../../../src/core/server'; @@ -93,6 +95,8 @@ export interface RuleTypeParams { references: References; version: Version; exceptionsList: ListArrayOrUndefined; + concurrentSearches: ConcurrentSearchesOrUndefined; + itemsPerSearch: ItemsPerSearchOrUndefined; } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index 4b5261edcdfd0e..6b10a9909e19cf 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -23,6 +23,10 @@ import { NEWS_FEED_URL_SETTING_DEFAULT, IP_REPUTATION_LINKS_SETTING, IP_REPUTATION_LINKS_SETTING_DEFAULT, + DEFAULT_RULES_TABLE_REFRESH_SETTING, + DEFAULT_RULE_REFRESH_INTERVAL_ON, + DEFAULT_RULE_REFRESH_INTERVAL_VALUE, + DEFAULT_RULE_REFRESH_IDLE_VALUE, } from '../common/constants'; export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { @@ -112,6 +116,31 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { requiresPageReload: true, schema: schema.boolean(), }, + [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { + name: i18n.translate('xpack.securitySolution.uiSettings.rulesTableRefresh', { + defaultMessage: 'Rules auto refresh', + }), + description: i18n.translate( + 'xpack.securitySolution.uiSettings.rulesTableRefreshDescription', + { + defaultMessage: + '

Enables auto refresh on the all rules and monitoring tables, in milliseconds

', + } + ), + type: 'json', + value: `{ + "on": ${DEFAULT_RULE_REFRESH_INTERVAL_ON}, + "value": ${DEFAULT_RULE_REFRESH_INTERVAL_VALUE}, + "idleTimeout": ${DEFAULT_RULE_REFRESH_IDLE_VALUE} +}`, + category: ['securitySolution'], + requiresPageReload: true, + schema: schema.object({ + idleTimeout: schema.number({ min: 300000 }), + value: schema.number({ min: 60000 }), + on: schema.boolean(), + }), + }, [NEWS_FEED_URL_SETTING]: { name: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrl', { defaultMessage: 'News feed URL', diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6548fb9752d445..e7784846598e47 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2727,7 +2727,6 @@ "inputControl.listControl.disableTooltip": "「{label}」が設定されるまで無効です。", "inputControl.listControl.unableToFetchTooltip": "用語を取得できません、エラー: {errorMessage}", "inputControl.rangeControl.unableToFetchTooltip": "範囲 (最低値と最高値) を取得できません、エラー: {errorMessage}", - "inputControl.register.controlsDescription": "ダッシュボードを簡単に操作できるように、インタラクティブなコントロールを作成します。", "inputControl.register.controlsTitle": "コントロール", "inputControl.register.tabs.controlsTitle": "コントロール", "inputControl.register.tabs.optionsTitle": "オプション", @@ -3972,7 +3971,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最終値", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "選択してください", "visTypeTimeseries.indexPatternLabel": "インデックスパターン", - "visTypeTimeseries.kbnVisTypes.metricsDescription": "ビジュアルパイプラインインターフェースを使用して時系列のチャートを作成します。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.markdown.alignOptions.bottomLabel": "一番下", "visTypeTimeseries.markdown.alignOptions.middleLabel": "真ん中", @@ -4327,7 +4325,6 @@ "visTypeVega.mapView.minZoomAndMaxZoomHaveBeenSwappedWarningMessage": "{minZoomPropertyName} と {maxZoomPropertyName} が交換されました", "visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "{name} を {max} にリセットしています", "visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "{name} を {min} にリセットしています", - "visTypeVega.type.vegaDescription": "Vega と Vega-Lite を使用してカスタムビジュアライゼーションを作成します。", "visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} には「{formLink}」の形で {urlParam} パラメーターが必要です", "visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "{urlObject} を使用するには {subObjectName} サブオブジェクトが必要です", "visTypeVega.vegaParser.autoSizeDoesNotAllowFalse": "{autoSizeParam}が有効です。無効にするには、{autoSizeParam}を{noneParam}に設定してください", @@ -4475,7 +4472,6 @@ "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", "visTypeVislib.gauge.alignmentHorizontalTitle": "横", "visTypeVislib.gauge.alignmentVerticalTitle": "縦", - "visTypeVislib.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値とリファレンスしきい値との関連性を示すのに使用します。", "visTypeVislib.gauge.gaugeTitle": "ゲージ", "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", "visTypeVislib.gauge.gaugeTypes.circleText": "円", @@ -4558,21 +4554,16 @@ "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", "visualizations.functions.visualization.help": "シンプルなビジュアライゼーションです", "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", - "visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", - "visualizations.newVisWizard.betaTitle": "ベータ", "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", - "visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。", "visualizations.newVisWizard.experimentalTitle": "実験的", "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", - "visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。", "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。", "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", "visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました", "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", - "visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください", "visualizations.newVisWizard.title": "新規ビジュアライゼーション", "visualizations.noResultsFoundTitle": "結果が見つかりませんでした", "visualizations.savedObjectName": "ビジュアライゼーション", @@ -10753,7 +10744,6 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "次へ", "xpack.lens.fittingFunctionsTitle.none": "非表示", "xpack.lens.fittingFunctionsTitle.zero": "ゼロ", - "xpack.lens.functions.mergeTables.help": "いくつかの Kibana 表を 1 つの表に結合するのをアシストします", "xpack.lens.functions.renameColumns.help": "データベースの列の名前の変更をアシストします", "xpack.lens.functions.renameColumns.idMap.help": "キーが古い列 ID で値が対応する新しい列 ID となるように JSON エンコーディングされたオブジェクトです。他の列 ID はすべてのそのままです。", "xpack.lens.includeValueButtonAriaLabel": "{value}を含める", @@ -10916,9 +10906,6 @@ "xpack.lens.sugegstion.refreshSuggestionLabel": "更新", "xpack.lens.suggestion.refreshSuggestionTooltip": "選択したビジュアライゼーションに基づいて、候補を更新します。", "xpack.lens.suggestions.currentVisLabel": "現在", - "xpack.lens.visTypeAlias.description": "レンズは基本的なビジュアライゼーションを作成するシンプルな方法です", - "xpack.lens.visTypeAlias.promotion.buttonText": "レンズに移動", - "xpack.lens.visTypeAlias.promotion.description": "レンズは直感的に使える新しいビジュアライゼーションの作成方法です。お試しください。", "xpack.lens.visTypeAlias.title": "レンズビジュアライゼーション", "xpack.lens.visTypeAlias.type": "レンズ", "xpack.lens.xyChart.addLayer": "レイヤーを追加", @@ -10937,7 +10924,6 @@ "xpack.lens.xyChart.chartTypeLabel": "チャートタイプ", "xpack.lens.xyChart.chartTypeLegend": "チャートタイプ", "xpack.lens.xyChart.emptyXLabel": "(空)", - "xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。", "xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義", "xpack.lens.xyChart.Gridlines": "グリッド線", "xpack.lens.xyChart.gridlinesSettings.help": "xおよびy軸のグリッド線を表示", @@ -11676,8 +11662,6 @@ "xpack.maps.viewControl.latLabel": "緯度:", "xpack.maps.viewControl.lonLabel": "経度:", "xpack.maps.viewControl.zoomLabel": "ズーム:", - "xpack.maps.visTypeAlias.description": "マップを作成し、複数のレイヤーとインデックスを使用して、スタイルを設定します。", - "xpack.maps.visTypeAlias.legacyMapVizWarning": "座標マップと地域マップの代わりに、マップアプリを使用します。\nマップアプリはより機能が豊富で、使いやすくなっています。", "xpack.maps.visTypeAlias.title": "マップ", "xpack.maps.xyztmssource.attributionLink": "属性テキストにはリンクが必要です", "xpack.maps.xyztmssource.attributionLinkLabel": "属性リンク", @@ -17841,7 +17825,6 @@ "xpack.securitySolution.footer.of": "/", "xpack.securitySolution.footer.rows": "行", "xpack.securitySolution.footer.totalCountOfEvents": "イベント", - "xpack.securitySolution.footer.updated": "更新しました", "xpack.securitySolution.formatted.duration.aFewMillisecondsTooltip": "数ミリ秒", "xpack.securitySolution.formatted.duration.aFewNanosecondsTooltip": "数ナノ秒", "xpack.securitySolution.formatted.duration.aMillisecondTooltip": "1 ミリ秒", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1cb1496cd9a06f..f3cd662bacba71 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2728,7 +2728,6 @@ "inputControl.listControl.disableTooltip": "设置 “{label}” 之前禁用。", "inputControl.listControl.unableToFetchTooltip": "无法提取词,错误:{errorMessage}", "inputControl.rangeControl.unableToFetchTooltip": "无法提取范围最小值和最大值,错误:{errorMessage}", - "inputControl.register.controlsDescription": "创建交互控件,以方便仪表板操控。", "inputControl.register.controlsTitle": "控件", "inputControl.register.tabs.controlsTitle": "控件", "inputControl.register.tabs.optionsTitle": "选项", @@ -3973,7 +3972,6 @@ "visTypeTimeseries.indexPattern.timeRange.lastValue": "最后值", "visTypeTimeseries.indexPattern.timeRange.selectTimeRange": "选择", "visTypeTimeseries.indexPatternLabel": "索引模式", - "visTypeTimeseries.kbnVisTypes.metricsDescription": "使用可视化管道界面构建时间序列", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.markdown.alignOptions.bottomLabel": "下", "visTypeTimeseries.markdown.alignOptions.middleLabel": "中", @@ -4328,7 +4326,6 @@ "visTypeVega.mapView.minZoomAndMaxZoomHaveBeenSwappedWarningMessage": "已交换 {minZoomPropertyName} 和 {maxZoomPropertyName}", "visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "将 {name} 重置为 {max}", "visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "将 {name} 重置为 {min}", - "visTypeVega.type.vegaDescription": "使用 Vega 和 Vega-Lite 创建定制可视化", "visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} 需要以“{formLink}”形式的 {urlParam} 参数", "visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "使用 {urlObject} 应具有 {subObjectName} 子对象", "visTypeVega.vegaParser.autoSizeDoesNotAllowFalse": "{autoSizeParam} 已启用,只能通过将 {autoSizeParam} 设置为 {noneParam} 来禁用", @@ -4477,7 +4474,6 @@ "visTypeVislib.gauge.alignmentAutomaticTitle": "自动", "visTypeVislib.gauge.alignmentHorizontalTitle": "水平", "visTypeVislib.gauge.alignmentVerticalTitle": "垂直", - "visTypeVislib.gauge.gaugeDescription": "仪表盘图指示指标的状态。用于显示指标值与参考阈值的相关程度。", "visTypeVislib.gauge.gaugeTitle": "仪表盘图", "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", "visTypeVislib.gauge.gaugeTypes.circleText": "圆形", @@ -4560,21 +4556,16 @@ "visualizations.function.visDimension.help": "生成 visConfig 维度对象", "visualizations.functions.visualization.help": "简单可视化", "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", - "visualizations.newVisWizard.betaDescription": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束", - "visualizations.newVisWizard.betaTitle": "公测版", "visualizations.newVisWizard.chooseSourceTitle": "选择源", - "visualizations.newVisWizard.experimentalDescription": "这是实验性可视化。与稳定的可视化相比,其设计和实现均不够成熟,可能会随时发生更改。", "visualizations.newVisWizard.experimentalTitle": "实验性", "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", "visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型", - "visualizations.newVisWizard.helpText": "通过为该可视化选择类型,来开始创建您的可视化。", "visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,来开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。", "visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}", "visualizations.newVisWizard.resultsFound": "找到了 {resultCount} 个{resultCount, plural, one {类型} other {类型} }", "visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。", "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "索引模式", "visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索", - "visualizations.newVisWizard.selectVisType": "选择可视化类型", "visualizations.newVisWizard.title": "新建可视化", "visualizations.noResultsFoundTitle": "找不到结果", "visualizations.savedObjectName": "可视化", @@ -10766,7 +10757,6 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "下一", "xpack.lens.fittingFunctionsTitle.none": "隐藏", "xpack.lens.fittingFunctionsTitle.zero": "零", - "xpack.lens.functions.mergeTables.help": "将任何数目的 kibana 表合并成单个表的助手", "xpack.lens.functions.renameColumns.help": "用于重命名数据表列的助手", "xpack.lens.functions.renameColumns.idMap.help": "旧列 ID 为键且相应新列 ID 为值的 JSON 编码对象。所有其他列 ID 都将保留。", "xpack.lens.includeValueButtonAriaLabel": "包括 {value}", @@ -10929,9 +10919,6 @@ "xpack.lens.sugegstion.refreshSuggestionLabel": "刷新", "xpack.lens.suggestion.refreshSuggestionTooltip": "基于选定可视化刷新建议。", "xpack.lens.suggestions.currentVisLabel": "当前", - "xpack.lens.visTypeAlias.description": "Lens 简化了基本可视化的创建", - "xpack.lens.visTypeAlias.promotion.buttonText": "前往 Lens", - "xpack.lens.visTypeAlias.promotion.description": "试用 Lens,以全新直观的方式创建可视化。", "xpack.lens.visTypeAlias.title": "Lens 可视化", "xpack.lens.visTypeAlias.type": "Lens", "xpack.lens.xyChart.addLayer": "添加图层", @@ -10950,7 +10937,6 @@ "xpack.lens.xyChart.chartTypeLabel": "图表类型", "xpack.lens.xyChart.chartTypeLegend": "图表类型", "xpack.lens.xyChart.emptyXLabel": "(空)", - "xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和面积图。", "xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式", "xpack.lens.xyChart.Gridlines": "网格线", "xpack.lens.xyChart.gridlinesSettings.help": "显示 x 和 y 轴网格线", @@ -11689,8 +11675,6 @@ "xpack.maps.viewControl.latLabel": "纬度:", "xpack.maps.viewControl.lonLabel": "经度:", "xpack.maps.viewControl.zoomLabel": "缩放:", - "xpack.maps.visTypeAlias.description": "使用多个图层和索引创建地图并提供样式。", - "xpack.maps.visTypeAlias.legacyMapVizWarning": "使用 Maps 应用而非坐标地图和区域地图。\nMaps 应用提供更多的功能并更加易用。", "xpack.maps.visTypeAlias.title": "Maps", "xpack.maps.xyztmssource.attributionLink": "属性文本必须附带链接", "xpack.maps.xyztmssource.attributionLinkLabel": "属性链接", @@ -17860,7 +17844,6 @@ "xpack.securitySolution.footer.of": "/", "xpack.securitySolution.footer.rows": "行", "xpack.securitySolution.footer.totalCountOfEvents": "事件", - "xpack.securitySolution.footer.updated": "已更新", "xpack.securitySolution.formatted.duration.aFewMillisecondsTooltip": "几毫秒", "xpack.securitySolution.formatted.duration.aFewNanosecondsTooltip": "几纳秒", "xpack.securitySolution.formatted.duration.aMillisecondTooltip": "一毫秒", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 662db81101eeee..70b6fb0b750dd5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -11,7 +11,10 @@ import { Alert, ActionType, ValidationResult } from '../../../../types'; import { EuiTitle, EuiBadge, EuiFlexItem, EuiSwitch, EuiButtonEmpty, EuiText } from '@elastic/eui'; import { ViewInApp } from './view_in_app'; import { coreMock } from 'src/core/public/mocks'; -import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { + AlertExecutionStatusErrorReasons, + ALERTS_FEATURE_ID, +} from '../../../../../../alerts/common'; const mockes = coreMock.createSetup(); @@ -125,7 +128,7 @@ describe('alert_details', () => { status: 'error', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: 'unknown', + reason: AlertExecutionStatusErrorReasons.Unknown, message: 'test', }, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 1272024557bb65..abd81279625618 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -301,6 +301,7 @@ export const AlertDetails: React.FunctionComponent = ({ ) : ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index e1287d299b6e92..25bbe977fd76ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; import { AlertInstances, AlertInstanceListItem, alertInstanceToListItem } from './alert_instances'; -import { Alert, AlertInstanceSummary, AlertInstanceStatus } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertInstanceStatus, AlertType } from '../../../../types'; import { EuiBasicTable } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -34,15 +34,18 @@ jest.mock('../../../app_context', () => { describe('alert_instances', () => { it('render a list of alert instances', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const alertInstanceSummary = mockAlertInstanceSummary({ instances: { first_instance: { status: 'OK', muted: false, + actionGroupId: 'default', }, second_instance: { status: 'Active', muted: false, + actionGroupId: 'action group id unknown', }, }, }); @@ -51,14 +54,14 @@ describe('alert_instances', () => { // active first alertInstanceToListItem( fakeNow.getTime(), - alert, + alertType, 'second_instance', alertInstanceSummary.instances.second_instance ), // ok second alertInstanceToListItem( fakeNow.getTime(), - alert, + alertType, 'first_instance', alertInstanceSummary.instances.first_instance ), @@ -69,6 +72,7 @@ describe('alert_instances', () => { @@ -80,6 +84,7 @@ describe('alert_instances', () => { it('render a hidden field with duration epoch', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const alertInstanceSummary = mockAlertInstanceSummary(); expect( @@ -88,6 +93,7 @@ describe('alert_instances', () => { durationEpoch={fake2MinutesAgo.getTime()} {...mockAPIs} alert={alert} + alertType={alertType} readOnly={false} alertInstanceSummary={alertInstanceSummary} /> @@ -99,6 +105,7 @@ describe('alert_instances', () => { it('render all active alert instances', () => { const alert = mockAlert(); + const alertType = mockAlertType(); const instances: Record = { ['us-central']: { status: 'OK', @@ -114,6 +121,7 @@ describe('alert_instances', () => { { .find(EuiBasicTable) .prop('items') ).toEqual([ - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-central', instances['us-central']), - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-east', instances['us-east']), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-central', instances['us-central']), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-east', instances['us-east']), ]); }); @@ -132,6 +140,7 @@ describe('alert_instances', () => { const alert = mockAlert({ mutedInstanceIds: ['us-west', 'us-east'], }); + const alertType = mockAlertType(); const instanceUsWest: AlertInstanceStatus = { status: 'OK', muted: false }; const instanceUsEast: AlertInstanceStatus = { status: 'OK', muted: false }; @@ -140,6 +149,7 @@ describe('alert_instances', () => { { .find(EuiBasicTable) .prop('items') ).toEqual([ - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-west', instanceUsWest), - alertInstanceToListItem(fakeNow.getTime(), alert, 'us-east', instanceUsEast), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-west', instanceUsWest), + alertInstanceToListItem(fakeNow.getTime(), alertType, 'us-east', instanceUsEast), ]); }); }); describe('alertInstanceToListItem', () => { it('handles active instances', () => { - const alert = mockAlert(); + const alertType = mockAlertType({ + actionGroups: [ + { id: 'default', name: 'Default Action Group' }, + { id: 'testing', name: 'Test Action Group' }, + ], + }); const start = fake2MinutesAgo; const instance: AlertInstanceStatus = { status: 'Active', muted: false, activeStartDate: fake2MinutesAgo.toISOString(), + actionGroupId: 'testing', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Test Action Group', healthColor: 'primary' }, start, sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), @@ -184,20 +200,38 @@ describe('alertInstanceToListItem', () => { }); }); - it('handles active muted instances', () => { - const alert = mockAlert({ - mutedInstanceIds: ['id'], + it('handles active instances with no action group id', () => { + const alertType = mockAlertType(); + const start = fake2MinutesAgo; + const instance: AlertInstanceStatus = { + status: 'Active', + muted: false, + activeStartDate: fake2MinutesAgo.toISOString(), + }; + + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ + instance: 'id', + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, + start, + sortPriority: 0, + duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), + isMuted: false, }); + }); + + it('handles active muted instances', () => { + const alertType = mockAlertType(); const start = fake2MinutesAgo; const instance: AlertInstanceStatus = { status: 'Active', muted: true, activeStartDate: fake2MinutesAgo.toISOString(), + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, start, sortPriority: 0, duration: fakeNow.getTime() - fake2MinutesAgo.getTime(), @@ -206,15 +240,16 @@ describe('alertInstanceToListItem', () => { }); it('handles active instances with start date', () => { - const alert = mockAlert(); + const alertType = mockAlertType(); const instance: AlertInstanceStatus = { status: 'Active', muted: false, + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', - status: { label: 'Active', healthColor: 'primary' }, + status: { label: 'Active', actionGroup: 'Default Action Group', healthColor: 'primary' }, start: undefined, duration: 0, sortPriority: 0, @@ -223,14 +258,13 @@ describe('alertInstanceToListItem', () => { }); it('handles muted inactive instances', () => { - const alert = mockAlert({ - mutedInstanceIds: ['id'], - }); + const alertType = mockAlertType(); const instance: AlertInstanceStatus = { status: 'OK', muted: true, + actionGroupId: 'default', }; - expect(alertInstanceToListItem(fakeNow.getTime(), alert, 'id', instance)).toEqual({ + expect(alertInstanceToListItem(fakeNow.getTime(), alertType, 'id', instance)).toEqual({ instance: 'id', status: { label: 'OK', healthColor: 'subdued' }, start: undefined, @@ -268,6 +302,23 @@ function mockAlert(overloads: Partial = {}): Alert { }; } +function mockAlertType(overloads: Partial = {}): AlertType { + return { + id: 'test.testAlertType', + name: 'My Test Alert Type', + actionGroups: [{ id: 'default', name: 'Default Action Group' }], + actionVariables: { + context: [], + state: [], + params: [], + }, + defaultActionGroupId: 'default', + authorizedConsumers: {}, + producer: 'alerts', + ...overloads, + }; +} + function mockAlertInstanceSummary( overloads: Partial = {} ): AlertInstanceSummary { @@ -288,6 +339,7 @@ function mockAlertInstanceSummary( foo: { status: 'OK', muted: false, + actionGroupId: 'testActionGroup', }, }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 0648f34927db32..ed05d81646c4af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -11,8 +11,14 @@ import { EuiBasicTable, EuiHealth, EuiSpacer, EuiSwitch } from '@elastic/eui'; // @ts-ignore import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '@elastic/eui/lib/services'; import { padStart, chunk } from 'lodash'; -import { AlertInstanceStatusValues } from '../../../../../../alerts/common'; -import { Alert, AlertInstanceSummary, AlertInstanceStatus, Pagination } from '../../../../types'; +import { ActionGroup, AlertInstanceStatusValues } from '../../../../../../alerts/common'; +import { + Alert, + AlertInstanceSummary, + AlertInstanceStatus, + AlertType, + Pagination, +} from '../../../../types'; import { ComponentOpts as AlertApis, withBulkAlertOperations, @@ -21,6 +27,7 @@ import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; type AlertInstancesProps = { alert: Alert; + alertType: AlertType; readOnly: boolean; alertInstanceSummary: AlertInstanceSummary; requestRefresh: () => Promise; @@ -48,7 +55,12 @@ export const alertInstancesTableColumns = ( { defaultMessage: 'Status' } ), render: (value: AlertInstanceListItemStatus, instance: AlertInstanceListItem) => { - return {value.label}; + return ( + + {value.label} + {value.actionGroup ? ` (${value.actionGroup})` : ``} + + ); }, sortable: false, 'data-test-subj': 'alertInstancesTableCell-status', @@ -113,6 +125,7 @@ function durationAsString(duration: Duration): string { export function AlertInstances({ alert, + alertType, readOnly, alertInstanceSummary, muteAlertInstance, @@ -127,7 +140,7 @@ export function AlertInstances({ const alertInstances = Object.entries(alertInstanceSummary.instances) .map(([instanceId, instance]) => - alertInstanceToListItem(durationEpoch, alert, instanceId, instance) + alertInstanceToListItem(durationEpoch, alertType, instanceId, instance) ) .sort((leftInstance, rightInstance) => leftInstance.sortPriority - rightInstance.sortPriority); @@ -180,6 +193,7 @@ function getPage(items: any[], pagination: Pagination) { interface AlertInstanceListItemStatus { label: string; healthColor: string; + actionGroup?: string; } export interface AlertInstanceListItem { instance: string; @@ -200,16 +214,28 @@ const INACTIVE_LABEL = i18n.translate( { defaultMessage: 'OK' } ); +function getActionGroupName(alertType: AlertType, actionGroupId?: string): string | undefined { + actionGroupId = actionGroupId || alertType.defaultActionGroupId; + const actionGroup = alertType?.actionGroups?.find( + (group: ActionGroup) => group.id === actionGroupId + ); + return actionGroup?.name; +} + export function alertInstanceToListItem( durationEpoch: number, - alert: Alert, + alertType: AlertType, instanceId: string, instance: AlertInstanceStatus ): AlertInstanceListItem { const isMuted = !!instance?.muted; const status = instance?.status === 'Active' - ? { label: ACTIVE_LABEL, healthColor: 'primary' } + ? { + label: ACTIVE_LABEL, + actionGroup: getActionGroupName(alertType, instance?.actionGroupId), + healthColor: 'primary', + } : { label: INACTIVE_LABEL, healthColor: 'subdued' }; const start = instance?.activeStartDate ? new Date(instance.activeStartDate) : undefined; const duration = start ? durationEpoch - start.valueOf() : 0; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index 603f06d0bbae44..3a171d469d4ad6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { shallow } from 'enzyme'; import { ToastsApi } from 'kibana/public'; import { AlertInstancesRoute, getAlertInstanceSummary } from './alert_instances_route'; -import { Alert, AlertInstanceSummary } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { EuiLoadingSpinner } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -23,10 +23,11 @@ jest.mock('../../../app_context', () => { describe('alert_instance_summary_route', () => { it('render a loader while fetching data', () => { const alert = mockAlert(); + const alertType = mockAlertType(); expect( shallow( - + ).containsMatchingElement() ).toBeTruthy(); }); @@ -140,6 +141,23 @@ function mockAlert(overloads: Partial = {}): Alert { }; } +function mockAlertType(overloads: Partial = {}): AlertType { + return { + id: 'test.testAlertType', + name: 'My Test Alert Type', + actionGroups: [{ id: 'default', name: 'Default Action Group' }], + actionVariables: { + context: [], + state: [], + params: [], + }, + defaultActionGroupId: 'default', + authorizedConsumers: {}, + producer: 'alerts', + ...overloads, + }; +} + function mockAlertInstanceSummary(overloads: Partial = {}): any { const summary: AlertInstanceSummary = { id: 'alert-id', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index 9137a26a32dd45..83a09e9eafcc15 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ToastsApi } from 'kibana/public'; import React, { useState, useEffect } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { Alert, AlertInstanceSummary } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { useAppDependencies } from '../../../app_context'; import { ComponentOpts as AlertApis, @@ -18,12 +18,14 @@ import { AlertInstancesWithApi as AlertInstances } from './alert_instances'; type WithAlertInstanceSummaryProps = { alert: Alert; + alertType: AlertType; readOnly: boolean; requestRefresh: () => Promise; } & Pick; export const AlertInstancesRoute: React.FunctionComponent = ({ alert, + alertType, readOnly, requestRefresh, loadAlertInstanceSummary: loadAlertInstanceSummary, @@ -48,6 +50,7 @@ export const AlertInstancesRoute: React.FunctionComponent diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 18cc7b540296ed..c434ca9d214028 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -17,7 +17,10 @@ import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; -import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { + AlertExecutionStatusErrorReasons, + ALERTS_FEATURE_ID, +} from '../../../../../../alerts/common'; import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ @@ -245,7 +248,7 @@ describe('alerts_list component with items', () => { status: 'error', lastExecutionDate: new Date('2020-08-20T19:23:38Z'), error: { - reason: 'unknown', + reason: AlertExecutionStatusErrorReasons.Unknown, message: 'test', }, }, diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index 775078a7e5df16..315b8f543b800e 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -145,6 +145,7 @@ export const PingType = t.intersection([ bytes: t.number, redirects: t.array(t.string), status_code: t.number, + headers: t.record(t.string, t.string), }), version: t.string, }), diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap new file mode 100644 index 00000000000000..ef707dc3ea3153 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/__snapshots__/ping_headers.test.tsx.snap @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Ping Headers shallow renders expected elements for valid props 1`] = ` + + + +

+ Response headers +

+ + } + id="responseHeaderAccord" + initialIsOpen={false} + isLoading={false} + isLoadingMessage={false} + paddingSize="none" + > + + +
+
+`; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx new file mode 100644 index 00000000000000..86394a0a4d8414 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/__tests__/ping_headers.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { PingHeaders } from '../headers'; + +describe('Ping Headers', () => { + const headers = { + 'Content-Type': 'text/html', + 'Content-Length': '174781', + Expires: 'Mon, 02 Nov 2020 17:22:03 GMT', + 'X-Xss-Protection': '0', + 'Accept-Ranges': 'bytes', + Date: 'Mon, 02 Nov 2020 17:22:03 GMT', + 'Cache-Control': 'private, max-age=0', + 'Alt-Svc': + 'h3-Q050=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"', + Server: 'sffe', + 'Last-Modified': 'Wed, 28 Oct 2020 18:45:00 GMT', + Vary: 'Accept-Encoding', + 'X-Content-Type-Options': 'nosniff', + }; + + it('shallow renders expected elements for valid props', () => { + expect(shallowWithIntl()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx index 6af38eca6b0e97..e6a9b1ebe9c841 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.tsx @@ -21,6 +21,7 @@ import { Ping, HttpResponseBody } from '../../../../common/runtime_types'; import { DocLinkForBody } from './doc_link_body'; import { PingRedirects } from './ping_redirects'; import { BrowserExpandedRow } from '../synthetics/browser_expanded_row'; +import { PingHeaders } from './headers'; interface Props { ping: Ping; @@ -105,6 +106,11 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => {
)} + {ping?.http?.response?.headers && ( + + + + )} diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx new file mode 100644 index 00000000000000..52fe26a7e08ca0 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/headers.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiAccordion, EuiDescriptionList, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + headers: Record; +} + +export const PingHeaders = ({ headers }: Props) => { + const headersList = Object.keys(headers) + .sort() + .map((header) => ({ + title: header, + description: headers[header], + })); + + return ( + <> + + +

+ {i18n.translate('xpack.uptime.pingList.headers.title', { + defaultMessage: 'Response headers', + })} +

+ + } + > + + +
+ + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap index ab38ee9adc6c2b..6fcce75cea70e5 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap @@ -1823,9 +1823,7 @@ exports[`EmptyState component renders error message when an error occurs 1`] = `
-

+

There was an error fetching your data.

diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx index 6328789d03f29f..0de5cd3ab31be5 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/empty_state.test.tsx @@ -42,7 +42,9 @@ describe('EmptyState component', () => { it(`renders error message when an error occurs`, () => { const errors: IHttpFetchError[] = [ - new HttpFetchError('There was an error fetching your data.', 'error', {} as any), + new HttpFetchError('There was an error fetching your data.', 'error', {} as any, {} as any, { + body: { message: 'There was an error fetching your data.' }, + }), ]; const component = mountWithRouter( diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx index f7b77df8497f95..165b123d8884db 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_error.tsx @@ -15,7 +15,7 @@ interface EmptyStateErrorProps { export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error: Error) => error.message && error.message.includes('unauthorized') + (error: IHttpFetchError) => error.message && error.message.includes('unauthorized') ); return ( @@ -46,7 +46,9 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error: Error) =>

{error.message}

)} + errors.map((error: IHttpFetchError) => ( +

{error.body.message || error.message}

+ ))}
} /> diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx index 1d8a7a771e0c51..352369cfdb72b8 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx @@ -17,6 +17,7 @@ import { MonitorListComponent, noItemsMessage } from '../monitor_list'; import { renderWithRouter, shallowWithRouter } from '../../../../lib'; import * as redux from 'react-redux'; import moment from 'moment'; +import { IHttpFetchError } from '../../../../../../../../src/core/public'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { @@ -187,7 +188,11 @@ describe('MonitorList component', () => { it('renders error list', () => { const component = shallowWithRouter( 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 5e0cc5d3dee1d4..f31e25484a9361 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 @@ -187,7 +187,7 @@ export const MonitorListComponent: ({ ( { @@ -41,7 +42,7 @@ export const monitorListReducer = handleActions( error: undefined, list: { ...action.payload }, }), - [String(getMonitorListFailure)]: (state: MonitorList, action: Action) => ({ + [String(getMonitorListFailure)]: (state: MonitorList, action: Action) => ({ ...state, error: action.payload, loading: false, diff --git a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index b75c729c2104a2..cd98ba1600d34b 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -9,17 +9,20 @@ import { IRouter, SavedObjectsClientContract, ISavedObjectsRepository, - ILegacyScopedClusterClient, + IScopedClusterClient, + ElasticsearchClient, } from 'src/core/server'; import { UMKibanaRoute } from '../../../rest_api'; import { PluginSetupContract } from '../../../../../features/server'; import { DynamicSettings } from '../../../../common/runtime_types'; import { MlPluginSetup as MlSetup } from '../../../../../ml/server'; -export type ESAPICaller = ILegacyScopedClusterClient['callAsCurrentUser']; - export type UMElasticsearchQueryFn = ( - params: { callES: ESAPICaller; dynamicSettings: DynamicSettings } & P + params: { + callES: ElasticsearchClient; + esClient?: IScopedClusterClient; + dynamicSettings: DynamicSettings; + } & P ) => Promise; export type UMSavedObjectsQueryFn = ( diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index a8969f2621f292..2126b484b1cfd3 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -5,10 +5,14 @@ */ import moment from 'moment'; -import { ISavedObjectsRepository, SavedObjectsClientContract } from 'kibana/server'; +import { + ISavedObjectsRepository, + ILegacyScopedClusterClient, + SavedObjectsClientContract, + ElasticsearchClient, +} from 'kibana/server'; import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PageViewParams, UptimeTelemetry, Usage } from './types'; -import { ESAPICaller } from '../framework'; import { savedObjectsAdapter } from '../../saved_objects'; interface UptimeTelemetryCollector { @@ -21,6 +25,8 @@ const BUCKET_SIZE = 3600; const BUCKET_NUMBER = 24; export class KibanaTelemetryAdapter { + public static callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] | ElasticsearchClient; + public static registerUsageCollector = ( usageCollector: UsageCollectionSetup, getSavedObjectsClient: () => ISavedObjectsRepository | undefined @@ -125,7 +131,7 @@ export class KibanaTelemetryAdapter { } public static async countNoOfUniqueMonitorAndLocations( - callCluster: ESAPICaller, + callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] | ElasticsearchClient, savedObjectsClient: ISavedObjectsRepository | SavedObjectsClientContract ) { const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); @@ -187,7 +193,11 @@ export class KibanaTelemetryAdapter { }, }; - const result = await callCluster('search', params); + const { body: result } = + typeof callCluster === 'function' + ? await callCluster('search', params) + : await callCluster.search(params); + const numberOfUniqueMonitors: number = result?.aggregations?.unique_monitors?.value ?? 0; const numberOfUniqueLocations: number = result?.aggregations?.unique_locations?.value ?? 0; const monitorNameStats: any = result?.aggregations?.monitor_name; diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 06b298aedeb2b5..ccb1e5a40ad2de 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -56,6 +56,8 @@ const mockOptions = ( services = alertsMock.createAlertServices(), state = {} ): any => { + services.scopedClusterClient = jest.fn() as any; + services.savedObjectsClient.get.mockResolvedValue({ id: '', type: '', @@ -282,7 +284,8 @@ describe('status check alert', () => { expect.assertions(5); toISOStringSpy.mockImplementation(() => 'foo date string'); const mockGetter: jest.Mock = jest.fn(); - mockGetter.mockReturnValue([ + + mockGetter.mockReturnValueOnce([ { monitorId: 'first', location: 'harrisburg', @@ -326,6 +329,7 @@ describe('status check alert', () => { const state = await alert.executor(options); const [{ value: alertInstanceMock }] = alertServices.alertInstanceFactory.mock.results; expect(mockGetter).toHaveBeenCalledTimes(1); + expect(mockGetter.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index 9dddc0035f6902..d4c26fe83b5fc1 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -12,12 +12,12 @@ import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { commonStateTranslations, durationAnomalyTranslations } from './translations'; import { AnomaliesTableRecord } from '../../../../ml/common/types/anomalies'; import { getSeverityType } from '../../../../ml/common/util/anomaly_utils'; -import { savedObjectsAdapter } from '../saved_objects'; import { UptimeCorePlugins } from '../adapters/framework'; import { UptimeAlertTypeFactory } from './types'; import { Ping } from '../../../common/runtime_types/ping'; import { getMLJobId } from '../../../common/lib'; import { getLatestMonitor } from '../requests/get_latest_monitor'; +import { uptimeAlertWrapper } from './uptime_alert_wrapper'; const { DURATION_ANOMALY } = ACTION_GROUP_DEFINITIONS; @@ -61,61 +61,58 @@ const getAnomalies = async ( ); }; -export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _libs, plugins) => ({ - id: 'xpack.uptime.alerts.durationAnomaly', - name: durationAnomalyTranslations.alertFactoryName, - validate: { - params: schema.object({ - monitorId: schema.string(), - severity: schema.number(), - }), - }, - defaultActionGroupId: DURATION_ANOMALY.id, - actionGroups: [ - { - id: DURATION_ANOMALY.id, - name: DURATION_ANOMALY.name, +export const durationAnomalyAlertFactory: UptimeAlertTypeFactory = (_server, _libs, plugins) => + uptimeAlertWrapper({ + id: 'xpack.uptime.alerts.durationAnomaly', + name: durationAnomalyTranslations.alertFactoryName, + validate: { + params: schema.object({ + monitorId: schema.string(), + severity: schema.number(), + }), }, - ], - actionVariables: { - context: [], - state: [...durationAnomalyTranslations.actionVariables, ...commonStateTranslations], - }, - producer: 'uptime', - async executor(options) { - const { - services: { alertInstanceFactory, callCluster, savedObjectsClient }, - state, - params, - } = options; + defaultActionGroupId: DURATION_ANOMALY.id, + actionGroups: [ + { + id: DURATION_ANOMALY.id, + name: DURATION_ANOMALY.name, + }, + ], + actionVariables: { + context: [], + state: [...durationAnomalyTranslations.actionVariables, ...commonStateTranslations], + }, + async executor({ options, esClient, savedObjectsClient, dynamicSettings }) { + const { + services: { alertInstanceFactory }, + state, + params, + } = options; - const { anomalies } = - (await getAnomalies(plugins, savedObjectsClient, params, state.lastCheckedAt)) ?? {}; + const { anomalies } = + (await getAnomalies(plugins, savedObjectsClient, params, state.lastCheckedAt)) ?? {}; - const foundAnomalies = anomalies?.length > 0; + const foundAnomalies = anomalies?.length > 0; - if (foundAnomalies) { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient - ); - const monitorInfo = await getLatestMonitor({ - dynamicSettings, - callES: callCluster, - dateStart: 'now-15m', - dateEnd: 'now', - monitorId: params.monitorId, - }); - anomalies.forEach((anomaly, index) => { - const alertInstance = alertInstanceFactory(DURATION_ANOMALY.id + index); - const summary = getAnomalySummary(anomaly, monitorInfo); - alertInstance.replaceState({ - ...updateState(state, false), - ...summary, + if (foundAnomalies) { + const monitorInfo = await getLatestMonitor({ + dynamicSettings, + callES: esClient, + dateStart: 'now-15m', + dateEnd: 'now', + monitorId: params.monitorId, + }); + anomalies.forEach((anomaly, index) => { + const alertInstance = alertInstanceFactory(DURATION_ANOMALY.id + index); + const summary = getAnomalySummary(anomaly, monitorInfo); + alertInstance.replaceState({ + ...updateState(state, false), + ...summary, + }); + alertInstance.scheduleActions(DURATION_ANOMALY.id); }); - alertInstance.scheduleActions(DURATION_ANOMALY.id); - }); - } + } - return updateState(state, foundAnomalies); - }, -}); + return updateState(state, foundAnomalies); + }, + }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index 7feb916046e3a0..b1b3666b40dc6b 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -26,7 +26,6 @@ import { GetMonitorStatusResult } from '../requests/get_monitor_status'; import { UNNAMED_LOCATION } from '../../../common/constants'; import { uptimeAlertWrapper } from './uptime_alert_wrapper'; import { MonitorStatusTranslations } from '../../../common/translations'; -import { ESAPICaller } from '../adapters/framework'; import { getUptimeIndexPattern, IndexPatternTitleAndFields } from '../requests/get_index_pattern'; import { UMServerLibs } from '../lib'; @@ -81,7 +80,6 @@ export const generateFilterDSL = async ( export const formatFilterString = async ( dynamicSettings: DynamicSettings, - callES: ESAPICaller, esClient: ElasticsearchClient, filters: StatusCheckFilters, search: string, @@ -90,9 +88,8 @@ export const formatFilterString = async ( await generateFilterDSL( () => libs?.requests?.getIndexPattern - ? libs?.requests?.getIndexPattern({ callES, esClient, dynamicSettings }) + ? libs?.requests?.getIndexPattern({ esClient, dynamicSettings }) : getUptimeIndexPattern({ - callES, esClient, dynamicSettings, }), @@ -237,12 +234,15 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = ], state: [...commonMonitorStateI18, ...commonStateTranslations], }, - async executor( - { params: rawParams, state, services: { alertInstanceFactory } }, - callES, + async executor({ + options: { + params: rawParams, + state, + services: { alertInstanceFactory }, + }, esClient, - dynamicSettings - ) { + dynamicSettings, + }) { const { filters, search, @@ -258,7 +258,6 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = const filterString = await formatFilterString( dynamicSettings, - callES, esClient, filters, search, @@ -278,7 +277,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = // after that shouldCheckStatus should be explicitly false if (!(!oldVersionTimeRange && shouldCheckStatus === false)) { downMonitorsByLocation = await libs.requests.getMonitorStatus({ - callES, + callES: esClient, dynamicSettings, timerange, numTimes, @@ -311,7 +310,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) = let availabilityResults: GetMonitorAvailabilityResult[] = []; if (shouldCheckAvailability) { availabilityResults = await libs.requests.getMonitorAvailability({ - callES, + callES: esClient, dynamicSettings, ...availability, filters: JSON.stringify(filterString) || undefined, diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index d4853ad7a9cb03..11f602d10bf51c 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -7,13 +7,13 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; import { UptimeAlertTypeFactory } from './types'; -import { savedObjectsAdapter } from '../saved_objects'; import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; +import { uptimeAlertWrapper } from './uptime_alert_wrapper'; const { TLS } = ACTION_GROUP_DEFINITIONS; @@ -82,74 +82,73 @@ export const getCertSummary = ( }; }; -export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ - id: 'xpack.uptime.alerts.tls', - name: tlsTranslations.alertFactoryName, - validate: { - params: schema.object({}), - }, - defaultActionGroupId: TLS.id, - actionGroups: [ - { - id: TLS.id, - name: TLS.name, +export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => + uptimeAlertWrapper({ + id: 'xpack.uptime.alerts.tls', + name: tlsTranslations.alertFactoryName, + validate: { + params: schema.object({}), }, - ], - actionVariables: { - context: [], - state: [...tlsTranslations.actionVariables, ...commonStateTranslations], - }, - producer: 'uptime', - async executor(options) { - const { - services: { alertInstanceFactory, callCluster, savedObjectsClient }, - state, - } = options; - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); - - const { certs, total }: CertResult = await libs.requests.getCerts({ - callES: callCluster, - dynamicSettings, - from: DEFAULT_FROM, - to: DEFAULT_TO, - index: 0, - size: DEFAULT_SIZE, - notValidAfter: `now+${ - dynamicSettings?.certExpirationThreshold ?? - DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold - }d`, - notValidBefore: `now-${ - dynamicSettings?.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold - }d`, - sortBy: 'common_name', - direction: 'desc', - }); - - const foundCerts = total > 0; - - if (foundCerts) { - const absoluteExpirationThreshold = moment() - .add( - dynamicSettings.certExpirationThreshold ?? - DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold, - 'd' - ) - .valueOf(); - const absoluteAgeThreshold = moment() - .subtract( - dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, - 'd' - ) - .valueOf(); - const alertInstance = alertInstanceFactory(TLS.id); - const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); - alertInstance.replaceState({ - ...updateState(state, foundCerts), - ...summary, + defaultActionGroupId: TLS.id, + actionGroups: [ + { + id: TLS.id, + name: TLS.name, + }, + ], + actionVariables: { + context: [], + state: [...tlsTranslations.actionVariables, ...commonStateTranslations], + }, + async executor({ options, dynamicSettings, esClient }) { + const { + services: { alertInstanceFactory }, + state, + } = options; + + const { certs, total }: CertResult = await libs.requests.getCerts({ + callES: esClient, + dynamicSettings, + from: DEFAULT_FROM, + to: DEFAULT_TO, + index: 0, + size: DEFAULT_SIZE, + notValidAfter: `now+${ + dynamicSettings?.certExpirationThreshold ?? + DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold + }d`, + notValidBefore: `now-${ + dynamicSettings?.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold + }d`, + sortBy: 'common_name', + direction: 'desc', }); - alertInstance.scheduleActions(TLS.id); - } - return updateState(state, foundCerts); - }, -}); + const foundCerts = total > 0; + + if (foundCerts) { + const absoluteExpirationThreshold = moment() + .add( + dynamicSettings.certExpirationThreshold ?? + DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold, + 'd' + ) + .valueOf(); + const absoluteAgeThreshold = moment() + .subtract( + dynamicSettings.certAgeThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, + 'd' + ) + .valueOf(); + const alertInstance = alertInstanceFactory(TLS.id); + const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); + alertInstance.replaceState({ + ...updateState(state, foundCerts), + ...summary, + }); + alertInstance.scheduleActions(TLS.id); + } + + return updateState(state, foundCerts); + }, + }); diff --git a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts index 390b6d347996c1..0961eb6557891e 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/uptime_alert_wrapper.ts @@ -4,18 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient, ElasticsearchClient } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { AlertExecutorOptions, AlertType, AlertTypeState } from '../../../../alerts/server'; import { savedObjectsAdapter } from '../saved_objects'; import { DynamicSettings } from '../../../common/runtime_types'; export interface UptimeAlertType extends Omit { - executor: ( - options: AlertExecutorOptions, - callES: ILegacyScopedClusterClient['callAsCurrentUser'], - esClient: ElasticsearchClient, - dynamicSettings: DynamicSettings - ) => Promise; + executor: ({ + options, + esClient, + dynamicSettings, + }: { + options: AlertExecutorOptions; + esClient: ElasticsearchClient; + dynamicSettings: DynamicSettings; + savedObjectsClient: SavedObjectsClientContract; + }) => Promise; } export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ @@ -23,13 +27,13 @@ export const uptimeAlertWrapper = (uptimeAlert: UptimeAlertType) => ({ producer: 'uptime', executor: async (options: AlertExecutorOptions) => { const { - services: { callCluster: callES, scopedClusterClient }, + services: { scopedClusterClient: esClient, savedObjectsClient }, } = options; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( options.services.savedObjectsClient ); - return uptimeAlert.executor(options, callES, scopedClusterClient, dynamicSettings); + return uptimeAlert.executor({ options, esClient, dynamicSettings, savedObjectsClient }); }, }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap index 97b97f84407584..6ab55c2afdddab 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/__snapshots__/get_monitor_charts.test.ts.snap @@ -2,7 +2,6 @@ exports[`ElasticsearchMonitorsAdapter getMonitorChartsData will provide expected filters 1`] = ` Array [ - "search", Object { "body": Object { "aggs": Object { @@ -26,9 +25,6 @@ Array [ "buckets": 25, "field": "@timestamp", }, - "date_histogram": Object { - "fixed_interval": "36000ms", - }, }, }, "query": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts index 4faaed53bebf2f..c0b94b19b75825 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts @@ -6,10 +6,10 @@ import { getCerts } from '../get_certs'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getCerts', () => { let mockHits: any; - let mockCallES: jest.Mock; beforeEach(() => { mockHits = [ @@ -79,17 +79,20 @@ describe('getCerts', () => { }, }, ]; - mockCallES = jest.fn(); - mockCallES.mockImplementation(() => ({ - hits: { - hits: mockHits, - }, - })); }); it('parses query result and returns expected values', async () => { + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce({ + body: { + hits: { + hits: mockHits, + }, + }, + } as any); + const result = await getCerts({ - callES: mockCallES, + callES: mockEsClient, dynamicSettings: { heartbeatIndices: 'heartbeat*', certAgeThreshold: DYNAMIC_SETTINGS_DEFAULTS.certAgeThreshold, @@ -126,10 +129,9 @@ describe('getCerts', () => { "total": 0, } `); - expect(mockCallES.mock.calls).toMatchInlineSnapshot(` + expect(mockEsClient.search.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - "search", Object { "body": Object { "_source": Array [ diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts index bd353b62df828e..9503174ed104c1 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts @@ -6,6 +6,7 @@ import { getLatestMonitor } from '../get_latest_monitor'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getLatestMonitor', () => { let expectedGetLatestSearchParams: any; @@ -44,29 +45,33 @@ describe('getLatestMonitor', () => { }, }; mockEsSearchResult = { - hits: { - hits: [ - { - _id: 'fejwio32', - _source: { - '@timestamp': '123456', - monitor: { - duration: { - us: 12345, + body: { + hits: { + hits: [ + { + _id: 'fejwio32', + _source: { + '@timestamp': '123456', + monitor: { + duration: { + us: 12345, + }, + id: 'testMonitor', + status: 'down', + type: 'http', }, - id: 'testMonitor', - status: 'down', - type: 'http', }, }, - }, - ], + ], + }, }, }; }); it('returns data in expected shape', async () => { - const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); + const result = await getLatestMonitor({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -94,6 +99,6 @@ describe('getLatestMonitor', () => { expect(result.timestamp).toBe('123456'); expect(result.monitor).not.toBeFalsy(); expect(result?.monitor?.id).toBe('testMonitor'); - expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetLatestSearchParams); + expect(mockEsClient.search).toHaveBeenCalledWith(expectedGetLatestSearchParams); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts index 015d9a4925f3eb..e8df65d4101679 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_availability.test.ts @@ -72,7 +72,7 @@ const genBucketItem = ({ describe('monitor availability', () => { describe('getMonitorAvailability', () => { it('applies bool filters to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -109,16 +109,15 @@ describe('monitor availability', () => { } }`; await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, filters: exampleFilter, range: 2, rangeUnit: 'w', threshold: '54', }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -245,7 +244,7 @@ describe('monitor availability', () => { }); it('fetches a single page of results', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -288,13 +287,12 @@ describe('monitor availability', () => { threshold: '69', }; const result = await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -458,7 +456,7 @@ describe('monitor availability', () => { }); it('fetches multiple pages', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -512,7 +510,7 @@ describe('monitor availability', () => { genBucketItem ); const result = await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, range: 3, rangeUnit: 'M', @@ -606,9 +604,8 @@ describe('monitor availability', () => { }, ] `); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(2); - expect(method).toEqual('search'); + const [params] = esMock.search.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(2); expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -701,9 +698,9 @@ describe('monitor availability', () => { "index": "heartbeat-8*", } `); - expect(esMock.callAsCurrentUser.mock.calls[1]).toMatchInlineSnapshot(` + + expect(esMock.search.mock.calls[1]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggs": Object { @@ -803,7 +800,7 @@ describe('monitor availability', () => { }); it('does not overwrite filters', async () => { - const [callES, esMock] = setupMockEsCompositeQuery< + const esMock = setupMockEsCompositeQuery< AvailabilityKey, GetMonitorAvailabilityResult, AvailabilityDoc @@ -816,14 +813,14 @@ describe('monitor availability', () => { genBucketItem ); await getMonitorAvailability({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, range: 3, rangeUnit: 's', threshold: '99', filters: JSON.stringify({ bool: { filter: [{ term: { 'monitor.id': 'foo' } }] } }), }); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts index 2ebe670bc43c1d..9edd3e2e160d24 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_charts.test.ts @@ -8,37 +8,37 @@ import { set } from '@elastic/safer-lodash-set'; import mockChartsData from './monitor_charts_mock.json'; import { getMonitorDurationChart } from '../get_monitor_duration'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('ElasticsearchMonitorsAdapter', () => { it('getMonitorChartsData will provide expected filters', async () => { expect.assertions(2); - const searchMock = jest.fn(); - const search = searchMock.bind({}); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); await getMonitorDurationChart({ - callES: search, + callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'fooID', dateStart: 'now-15m', dateEnd: 'now', }); - expect(searchMock).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); // protect against possible rounding errors polluting the snapshot comparison set( - searchMock.mock.calls[0][1], + mockEsClient.search.mock.calls[0], 'body.aggs.timeseries.date_histogram.fixed_interval', '36000ms' ); - expect(searchMock.mock.calls[0]).toMatchSnapshot(); + expect(mockEsClient.search.mock.calls[0]).toMatchSnapshot(); }); it('inserts empty buckets for missing data', async () => { - const searchMock = jest.fn(); - searchMock.mockReturnValue(mockChartsData); - const search = searchMock.bind({}); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockChartsData as any); + expect( await getMonitorDurationChart({ - callES: search, + callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, monitorId: 'id', dateStart: 'now-15m', diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts index e61d736e371061..949bc39f072593 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_monitor_status.test.ts @@ -42,7 +42,7 @@ const genBucketItem = ({ describe('getMonitorStatus', () => { it('applies bool filters to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [], genBucketItem ); @@ -78,7 +78,7 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, filters: exampleFilter, locations: [], @@ -88,9 +88,8 @@ describe('getMonitorStatus', () => { to: 'now-1m', }, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -190,12 +189,12 @@ describe('getMonitorStatus', () => { }); it('applies locations to params', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [], genBucketItem ); await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: ['fairbanks', 'harrisburg'], numTimes: 1, @@ -204,9 +203,8 @@ describe('getMonitorStatus', () => { to: 'now', }, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -291,7 +289,7 @@ describe('getMonitorStatus', () => { }); it('properly assigns filters for complex kuery filters', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [{ bucketCriteria: [] }], genBucketItem ); @@ -353,12 +351,12 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -476,7 +474,7 @@ describe('getMonitorStatus', () => { }); it('properly assigns filters for complex kuery filters object', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [{ bucketCriteria: [] }], genBucketItem ); @@ -498,12 +496,12 @@ describe('getMonitorStatus', () => { }, }; await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [, params] = esMock.callAsCurrentUser.mock.calls[0]; + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -581,7 +579,7 @@ describe('getMonitorStatus', () => { }); it('fetches single page of results', async () => { - const [callES, esMock] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( [ { bucketCriteria: [ @@ -618,13 +616,12 @@ describe('getMonitorStatus', () => { }, }; const result = await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, ...clientParameters, }); - expect(esMock.callAsCurrentUser).toHaveBeenCalledTimes(1); - const [method, params] = esMock.callAsCurrentUser.mock.calls[0]; - expect(method).toEqual('search'); + expect(esMock.search).toHaveBeenCalledTimes(1); + const [params] = esMock.search.mock.calls[0]; expect(params).toMatchInlineSnapshot(` Object { "body": Object { @@ -792,12 +789,12 @@ describe('getMonitorStatus', () => { ], }, ]; - const [callES] = setupMockEsCompositeQuery( + const esMock = setupMockEsCompositeQuery( criteria, genBucketItem ); const result = await getMonitorStatus({ - callES, + callES: esMock, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, locations: [], numTimes: 5, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts index ac940ffb6676f0..86e5f2876ca28b 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_ping_histogram.test.ts @@ -6,6 +6,7 @@ import { getPingHistogram } from '../get_ping_histogram'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getPingHistogram', () => { const standardMockResponse: any = { @@ -37,25 +38,28 @@ describe('getPingHistogram', () => { it.skip('returns a single bucket if array has 1', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - down: { - doc_count: 1, - }, - }, - ], - interval: '10s', + ], + interval: '10s', + }, }, }, - }); + } as any); const result = await getPingHistogram({ callES: mockEsClient, @@ -64,16 +68,20 @@ describe('getPingHistogram', () => { to: 'now', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('returns expected result for no status filter', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); + + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); standardMockResponse.aggregations.timeseries.interval = '1m'; - mockEsClient.mockReturnValue(standardMockResponse); + + mockEsClient.search.mockResolvedValueOnce({ + body: standardMockResponse, + } as any); const result = await getPingHistogram({ callES: mockEsClient, @@ -83,50 +91,53 @@ describe('getPingHistogram', () => { filters: '', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('handles status + additional user queries', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, - }, - down: { - doc_count: 1, - }, - }, - { - key: 2, - up: { - doc_count: 2, - }, - down: { - doc_count: 2, + + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - }, - { - key: 3, - up: { - doc_count: 3, + { + key: 2, + up: { + doc_count: 2, + }, + down: { + doc_count: 2, + }, }, - down: { - doc_count: 1, + { + key: 3, + up: { + doc_count: 3, + }, + down: { + doc_count: 1, + }, }, - }, - ], - interval: '1h', + ], + interval: '1h', + }, }, }, - }); + } as any); const searchFilter = { bool: { @@ -146,50 +157,52 @@ describe('getPingHistogram', () => { monitorId: undefined, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); it('handles simple_text_query without issues', async () => { expect.assertions(2); - const mockEsClient = jest.fn(); - - mockEsClient.mockReturnValue({ - aggregations: { - timeseries: { - buckets: [ - { - key: 1, - up: { - doc_count: 2, + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce({ + body: { + aggregations: { + timeseries: { + buckets: [ + { + key: 1, + up: { + doc_count: 2, + }, + down: { + doc_count: 1, + }, }, - down: { - doc_count: 1, + { + key: 2, + up: { + doc_count: 1, + }, + down: { + doc_count: 2, + }, }, - }, - { - key: 2, - up: { - doc_count: 1, - }, - down: { - doc_count: 2, + { + key: 3, + up: { + doc_count: 3, + }, + down: { + doc_count: 1, + }, }, - }, - { - key: 3, - up: { - doc_count: 3, - }, - down: { - doc_count: 1, - }, - }, - ], - interval: '1m', + ], + interval: '1m', + }, }, }, - }); + } as any); const filters = `{"bool":{"must":[{"simple_query_string":{"query":"http"}}]}}`; const result = await getPingHistogram({ @@ -200,7 +213,7 @@ describe('getPingHistogram', () => { filters, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); expect(result).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts index cb84cc2eb05b6e..f313cce9f758bb 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_pings.test.ts @@ -7,6 +7,7 @@ import { getPings } from '../get_pings'; import { set } from '@elastic/safer-lodash-set'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../common/constants'; +import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; describe('getAll', () => { let mockEsSearchResult: any; @@ -49,15 +50,17 @@ describe('getAll', () => { }, ]; mockEsSearchResult = { - hits: { - total: { - value: mockHits.length, + body: { + hits: { + total: { + value: mockHits.length, + }, + hits: mockHits, }, - hits: mockHits, - }, - aggregations: { - locations: { - buckets: [{ key: 'foo' }], + aggregations: { + locations: { + buckets: [{ key: 'foo' }], + }, }, }, }; @@ -84,8 +87,9 @@ describe('getAll', () => { }); it('returns data in the appropriate shape', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); const result = await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -102,12 +106,12 @@ describe('getAll', () => { expect(pings[0].timestamp).toBe('2018-10-30T18:51:59.792Z'); expect(pings[1].timestamp).toBe('2018-10-30T18:53:59.792Z'); expect(pings[2].timestamp).toBe('2018-10-30T18:55:59.792Z'); - expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); }); it('creates appropriate sort and size parameters', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -117,10 +121,9 @@ describe('getAll', () => { }); set(expectedGetAllParams, 'body.sort[0]', { timestamp: { order: 'asc' } }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -186,8 +189,8 @@ describe('getAll', () => { }); it('omits the sort param when no sort passed', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -195,10 +198,9 @@ describe('getAll', () => { size: 12, }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -264,8 +266,8 @@ describe('getAll', () => { }); it('omits the size param when no size passed', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -273,10 +275,9 @@ describe('getAll', () => { sort: 'desc', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -342,8 +343,8 @@ describe('getAll', () => { }); it('adds a filter for monitor ID', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -351,10 +352,9 @@ describe('getAll', () => { monitorId: 'testmonitorid', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { @@ -425,8 +425,8 @@ describe('getAll', () => { }); it('adds a filter for monitor status', async () => { - const mockEsClient = jest.fn(); - mockEsClient.mockReturnValue(mockEsSearchResult); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + mockEsClient.search.mockResolvedValueOnce(mockEsSearchResult); await getPings({ callES: mockEsClient, dynamicSettings: DYNAMIC_SETTINGS_DEFAULTS, @@ -434,10 +434,9 @@ describe('getAll', () => { status: 'down', }); - expect(mockEsClient).toHaveBeenCalledTimes(1); - expect(mockEsClient.mock.calls[0]).toMatchInlineSnapshot(` + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(mockEsClient.search.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "search", Object { "body": Object { "aggregations": Object { diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts index 878569b5d390f0..4ebc9b2da78558 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/helper.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyScopedClusterClient } from 'src/core/server'; import { elasticsearchServiceMock } from '../../../../../../../src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ElasticsearchClientMock } from '../../../../../../../src/core/server/elasticsearch/client/mocks'; + export interface MultiPageCriteria { after_key?: K; bucketCriteria: T[]; } -export type MockCallES = (method: any, params: any) => Promise; - /** * This utility function will set up a mock ES client, and store subsequent calls. It is designed * to let callers easily simulate an arbitrary series of chained composite aggregation calls by supplying @@ -30,8 +30,8 @@ export type MockCallES = (method: any, params: any) => Promise; export const setupMockEsCompositeQuery = ( criteria: Array>, genBucketItem: (criteria: C) => I -): [MockCallES, jest.Mocked>] => { - const esMock = elasticsearchServiceMock.createLegacyScopedClusterClient(); +): ElasticsearchClientMock => { + const esMock = elasticsearchServiceMock.createElasticsearchClient(); // eslint-disable-next-line @typescript-eslint/naming-convention criteria.forEach(({ after_key, bucketCriteria }) => { @@ -43,8 +43,14 @@ export const setupMockEsCompositeQuery = ( }, }, }; - esMock.callAsCurrentUser.mockResolvedValueOnce(mockResponse); + esMock.search.mockResolvedValueOnce({ + body: mockResponse, + statusCode: 200, + headers: {}, + warnings: [], + meta: {} as any, + }); }); - return [(method: any, params: any) => esMock.callAsCurrentUser(method, params), esMock]; + return esMock; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json b/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json index c62e862a9af89a..9fbfdb98d7fa44 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/monitor_charts_mock.json @@ -1,146 +1,318 @@ { - "took": 40, - "timed_out": false, - "_shards": { - "total": 1, - "successful": 1, - "skipped": 0, - "failed": 0 - }, - "aggregations": { - "timeseries": { - "buckets": [ - { - "key": 1568411568000, - "doc_count": 4, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 4658759 } }, - { "key": "us-west-4", "duration": { "avg": 8678399.5 } } - ] + "body": { + "took": 40, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "aggregations": { + "timeseries": { + "buckets": [ + { + "key": 1568411568000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 4658759 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 8678399.5 + } + } + ] + } + }, + { + "key": 1568411604000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411640000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 481780 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 685056.5 + } + } + ] + } + }, + { + "key": 1568411784000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 469206.5 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 261406.5 + } + } + ] + } + }, + { + "key": 1568411820000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411856000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411892000, + "doc_count": 0, + "location": { + "buckets": [] + } + }, + { + "key": 1568411928000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1999309.6666667 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 645563 + } + } + ] + } + }, + { + "key": 1568411964000, + "doc_count": 7, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 2499799.25 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 1513896.6666667 + } + } + ] + } + }, + { + "key": 1568412036000, + "doc_count": 5, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1876155.3333333 + } + }, + { + "key": "us-east-2", + "duration": { + "avg": 1511409 + } + } + ] + } + }, + { + "key": 1568412072000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1490845.75 + } + } + ] + } + }, + { + "key": 1568412108000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 2365962.6666667 + } + } + ] + } + }, + { + "key": 1568412144000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1788901.25 + } + } + ] + } + }, + { + "key": 1568412180000, + "doc_count": 4, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1773177.5 + } + } + ] + } + }, + { + "key": 1568412216000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 3086220.3333333 + } + } + ] + } + }, + { + "key": 1568412252000, + "doc_count": 1, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1020528 + } + } + ] + } + }, + { + "key": 1568412288000, + "doc_count": 3, + "location": { + "buckets": [ + { + "key": "us-west-4", + "duration": { + "avg": 1643963.3333333 + } + } + ] + } + }, + { + "key": 1568412324000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1804116 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 1799630 + } + } + ] + } + }, + { + "key": 1568412432000, + "doc_count": 8, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1972483.25 + } + }, + { + "key": "us-west-4", + "duration": { + "avg": 1543307.5 + } + } + ] + } + }, + { + "key": 1568412468000, + "doc_count": 1, + "location": { + "buckets": [ + { + "key": "us-east-2", + "duration": { + "avg": 1020490 + } + } + ] + } } - }, - { "key": 1568411604000, "doc_count": 0, "location": { "buckets": [] } }, - { - "key": 1568411640000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 481780 } }, - { "key": "us-west-4", "duration": { "avg": 685056.5 } } - ] - } - }, - { - "key": 1568411784000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 469206.5 } }, - { "key": "us-west-4", "duration": { "avg": 261406.5 } } - ] - } - }, - { "key": 1568411820000, "doc_count": 0, "location": { "buckets": [] } }, - { "key": 1568411856000, "doc_count": 0, "location": { "buckets": [] } }, - { "key": 1568411892000, "doc_count": 0, "location": { "buckets": [] } }, - { - "key": 1568411928000, - "doc_count": 4, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 1999309.6666667 } }, - { "key": "us-east-2", "duration": { "avg": 645563 } } - ] - } - }, - { - "key": 1568411964000, - "doc_count": 7, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 2499799.25 } }, - { "key": "us-east-2", "duration": { "avg": 1513896.6666667 } } - ] - } - }, - { - "key": 1568412036000, - "doc_count": 5, - "location": { - "buckets": [ - { "key": "us-west-4", "duration": { "avg": 1876155.3333333 } }, - { "key": "us-east-2", "duration": { "avg": 1511409 } } - ] - } - }, - { - "key": 1568412072000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1490845.75 } }] } - }, - { - "key": 1568412108000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 2365962.6666667 } }] - } - }, - { - "key": 1568412144000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1788901.25 } }] } - }, - { - "key": 1568412180000, - "doc_count": 4, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1773177.5 } }] } - }, - { - "key": 1568412216000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 3086220.3333333 } }] - } - }, - { - "key": 1568412252000, - "doc_count": 1, - "location": { "buckets": [{ "key": "us-west-4", "duration": { "avg": 1020528 } }] } - }, - { - "key": 1568412288000, - "doc_count": 3, - "location": { - "buckets": [{ "key": "us-west-4", "duration": { "avg": 1643963.3333333 } }] - } - }, - { - "key": 1568412324000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 1804116 } }, - { "key": "us-west-4", "duration": { "avg": 1799630 } } - ] - } - }, - { - "key": 1568412432000, - "doc_count": 8, - "location": { - "buckets": [ - { "key": "us-east-2", "duration": { "avg": 1972483.25 } }, - { "key": "us-west-4", "duration": { "avg": 1543307.5 } } - ] - } - }, - { - "key": 1568412468000, - "doc_count": 1, - "location": { "buckets": [{ "key": "us-east-2", "duration": { "avg": 1020490 } }] } - } - ] + ] + } } } } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 4793d420cbfd83..0836cb039b215e 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -145,7 +145,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn } // console.log(JSON.stringify(params, null, 2)); - const result = await callES('search', params); + const { body: result } = await callES.search(params); const certs = (result?.hits?.hits ?? []).map((hit: any) => { const { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts index e89b457eccf32d..c3295d6dd9c8f7 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_filter_bar.ts @@ -93,6 +93,8 @@ export const getFilterBar: UMElasticsearchQueryFn = async ({ esClient, dynamicSettings }) => { +export const getUptimeIndexPattern = async ({ + esClient, + dynamicSettings, +}: { + esClient: ElasticsearchClient; + dynamicSettings: DynamicSettings; +}): Promise => { const indexPatternsFetcher = new IndexPatternsFetcher(esClient); // Since `getDynamicIndexPattern` is called in setup_request (and thus by every endpoint) @@ -28,12 +31,10 @@ export const getUptimeIndexPattern: UMElasticsearchQueryFn< pattern: dynamicSettings.heartbeatIndices, }); - const indexPattern: IndexPatternTitleAndFields = { + return { fields, title: dynamicSettings.heartbeatIndices, }; - - return indexPattern; } catch (e) { const notExists = e.output?.statusCode === 404; if (notExists) { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts index 7688f04f1acd95..061d002b010de4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts @@ -12,9 +12,11 @@ export const getIndexStatus: UMElasticsearchQueryFn<{}, StatesIndexStatus> = asy dynamicSettings, }) => { const { - _shards: { total }, - count, - } = await callES('count', { index: dynamicSettings.heartbeatIndices }); + body: { + _shards: { total }, + count, + }, + } = await callES.count({ index: dynamicSettings.heartbeatIndices }); return { indexExists: total > 0, docCount: count, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts index f726ef47915b89..bff3aaf1176df3 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts @@ -42,7 +42,7 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< _source: ['synthetics.blob'], }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); if (!Array.isArray(result?.hits?.hits) || result.hits.hits.length < 1) { return null; } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index 9c139b2ce85886..f36815a747db3d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -42,7 +42,7 @@ export const getJourneySteps: UMElasticsearchQueryFn h?._source?.synthetics?.type === 'step/screenshot') .map((h: any) => h?._source?.synthetics?.step?.index); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts index d32b78bdc71394..f6562eaa42e900 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts @@ -57,7 +57,7 @@ export const getLatestMonitor: UMElasticsearchQueryFn { +const getMonitorAlerts = async ({ + callES, + dynamicSettings, + alertsClient, + monitorId, +}: { + callES: ElasticsearchClient; + dynamicSettings: any; + alertsClient: any; + monitorId: string; +}) => { const options: any = { page: 1, perPage: 500, @@ -70,13 +73,12 @@ const getMonitorAlerts = async ( const parsedFilters = await formatFilterString( dynamicSettings, callES, - esClient, currAlert.params.filters, currAlert.params.search ); esParams.body.query.bool = Object.assign({}, esParams.body.query.bool, parsedFilters?.bool); - const result = await callES('search', esParams); + const { body: result } = await callES.search(esParams); if (result.hits.total.value > 0) { monitorAlerts.push(currAlert); @@ -88,7 +90,7 @@ const getMonitorAlerts = async ( export const getMonitorDetails: UMElasticsearchQueryFn< GetMonitorDetailsParams, MonitorDetails -> = async ({ callES, esClient, dynamicSettings, monitorId, dateStart, dateEnd, alertsClient }) => { +> = async ({ callES, dynamicSettings, monitorId, dateStart, dateEnd, alertsClient }) => { const queryFilters: any = [ { range: { @@ -132,19 +134,19 @@ export const getMonitorDetails: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const data = result.hits.hits[0]?._source; const monitorError: MonitorError | undefined = data?.error; const errorTimestamp: string | undefined = data?.['@timestamp']; - const monAlerts = await getMonitorAlerts( + const monAlerts = await getMonitorAlerts({ callES, - esClient, dynamicSettings, alertsClient, - monitorId - ); + monitorId, + }); + return { monitorId, error: monitorError, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts index 00ca1b58783297..77ae7570a96a84 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_duration.ts @@ -59,7 +59,7 @@ export const getMonitorDurationChart: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const dateHistogramBuckets: any[] = result?.aggregations?.timeseries?.buckets ?? []; 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 f52e965d488ea9..b5183ca9ffb9fc 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 @@ -88,7 +88,7 @@ export const getMonitorLocations: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const locations = result?.aggregations?.location?.buckets ?? []; const getGeo = (locGeo: { name: string; location?: string }) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts index 3e49a32881f542..020fcf5331188d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_states.ts @@ -153,7 +153,7 @@ export const getHistogramForMonitors = async ( }; const result = await queryContext.search(params); - const histoBuckets: any[] = result.aggregations.histogram.buckets; + const histoBuckets: any[] = result.aggregations?.histogram.buckets ?? []; const simplified = histoBuckets.map((histoBucket: any): { timestamp: number; byId: any } => { const byId: { [key: string]: number } = {}; histoBucket.by_id.buckets.forEach((idBucket: any) => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts index caf505610e991b..06648d68969c14 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_monitor_status.ts @@ -133,7 +133,7 @@ export const getMonitorStatus: UMElasticsearchQueryFn< esParams.body.aggs.monitors.composite.after = afterKey; } - const result = await callES('search', esParams); + const { body: result } = await callES.search(esParams); afterKey = result?.aggregations?.monitors?.after_key; monitors = monitors.concat(result?.aggregations?.monitors?.buckets || []); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts index 5d8706e2fc5f17..4eb2d862cb7023 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_ping_histogram.ts @@ -76,7 +76,7 @@ export const getPingHistogram: UMElasticsearchQueryFn< }, }; - const result = await callES('search', params); + const { body: result } = await callES.search(params); const buckets: HistogramQueryResult[] = result?.aggregations?.timeseries?.buckets ?? []; const histogram = buckets.map((bucket) => { const x: number = bucket.key; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts index 03ec2d7343c9ab..e72b16de3d66f4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_pings.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_pings.ts @@ -108,9 +108,11 @@ export const getPings: UMElasticsearchQueryFn = a } const { - hits: { hits, total }, - aggregations: aggs, - } = await callES('search', params); + body: { + hits: { hits, total }, + aggregations: aggs, + }, + } = await callES.search(params); const locations = aggs?.locations ?? { buckets: [{ key: 'N/A', doc_count: 0 }] }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts index 92295a38cffb4f..ac36585ff09397 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_snapshot_counts.ts @@ -39,7 +39,7 @@ export const getSnapshotCount: UMElasticsearchQueryFn => { - const res = await context.search({ + const { body: res } = await context.search({ index: context.heartbeatIndices, body: statusCountBody(await context.dateAndCustomFilters()), }); diff --git a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts index 6c229cf30e165f..38e7dabb19941b 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/find_potential_matches.ts @@ -19,7 +19,7 @@ export const findPotentialMatches = async ( searchAfter: any, size: number ) => { - const queryResult = await query(queryContext, searchAfter, size); + const { body: queryResult } = await query(queryContext, searchAfter, size); const monitorIds: string[] = []; get(queryResult, 'aggregations.monitors.buckets', []).forEach((b: any) => { const monitorId = b.key.monitor_id; diff --git a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts index 5d97e635f3e7d7..96df8ea651c44a 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/query_context.ts @@ -5,13 +5,13 @@ */ import moment from 'moment'; -import { LegacyAPICaller } from 'src/core/server'; +import { ElasticsearchClient } from 'kibana/server'; import { CursorPagination } from './types'; import { parseRelativeDate } from '../../helper'; import { CursorDirection, SortOrder } from '../../../../common/runtime_types'; export class QueryContext { - callES: LegacyAPICaller; + callES: ElasticsearchClient; heartbeatIndices: string; dateRangeStart: string; dateRangeEnd: string; @@ -43,12 +43,12 @@ export class QueryContext { async search(params: any): Promise { params.index = this.heartbeatIndices; - return this.callES('search', params); + return this.callES.search(params); } async count(params: any): Promise { params.index = this.heartbeatIndices; - return this.callES('count', params); + return this.callES.count(params); } async dateAndCustomFilters(): Promise { diff --git a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index a864bfa591424e..6be9f813016f80 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -23,7 +23,7 @@ export const refinePotentialMatches = async ( return []; } - const queryResult = await query(queryContext, potentialMatchMonitorIDs); + const { body: queryResult } = await query(queryContext, potentialMatchMonitorIDs); return await fullyMatchingIds(queryResult, queryContext.statusFilter); }; diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts index baf999158a29e4..418cde9e701d50 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts @@ -17,8 +17,7 @@ export const createGetIndexPatternRoute: UMRestApiRouteFactory = (libs: UMServer return response.ok({ body: { ...(await libs.requests.getIndexPattern({ - callES, - esClient: _context.core.elasticsearch.client.asCurrentUser, + esClient: callES, dynamicSettings, })), }, diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts index 0e2c8c180e0e05..7b461060bf4bce 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts @@ -25,44 +25,51 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ tags: ['access:uptime-read'], }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { - const { - dateRangeStart, - dateRangeEnd, - filters, - pagination, - statusFilter, - pageSize, - } = request.query; - - const decodedPagination = pagination - ? JSON.parse(decodeURIComponent(pagination)) - : CONTEXT_DEFAULTS.CURSOR_PAGINATION; - const [indexStatus, { summaries, nextPagePagination, prevPagePagination }] = await Promise.all([ - libs.requests.getIndexStatus({ callES, dynamicSettings }), - libs.requests.getMonitorStates({ - callES, - dynamicSettings, + try { + const { dateRangeStart, dateRangeEnd, - pagination: decodedPagination, - pageSize, filters, - // this is added to make typescript happy, - // this sort of reassignment used to be further downstream but I've moved it here - // because this code is going to be decomissioned soon - statusFilter: statusFilter || undefined, - }), - ]); + pagination, + statusFilter, + pageSize, + } = request.query; + + const decodedPagination = pagination + ? JSON.parse(decodeURIComponent(pagination)) + : CONTEXT_DEFAULTS.CURSOR_PAGINATION; + const [ + indexStatus, + { summaries, nextPagePagination, prevPagePagination }, + ] = await Promise.all([ + libs.requests.getIndexStatus({ callES, dynamicSettings }), + libs.requests.getMonitorStates({ + callES, + dynamicSettings, + dateRangeStart, + dateRangeEnd, + pagination: decodedPagination, + pageSize, + filters, + // this is added to make typescript happy, + // this sort of reassignment used to be further downstream but I've moved it here + // because this code is going to be decomissioned soon + statusFilter: statusFilter || undefined, + }), + ]); - const totalSummaryCount = indexStatus?.docCount ?? 0; + const totalSummaryCount = indexStatus?.docCount ?? 0; - return response.ok({ - body: { - summaries, - nextPagePagination, - prevPagePagination, - totalSummaryCount, - }, - }); + return response.ok({ + body: { + summaries, + nextPagePagination, + prevPagePagination, + totalSummaryCount, + }, + }); + } catch (e) { + return response.internalError({ body: { message: e.message } }); + } }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts index 8bbb4fcb5575c2..bb54effc0d57e8 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts @@ -28,7 +28,6 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ body: { ...(await libs.requests.getMonitorDetails({ callES, - esClient: context.core.elasticsearch.client.asCurrentUser, dynamicSettings, monitorId, dateStart, diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index 589cb82d550f67..5e5f4a2a991cfd 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -9,12 +9,13 @@ import { RequestHandler, RouteConfig, RouteMethod, - LegacyCallAPIOptions, SavedObjectsClientContract, RequestHandlerContext, KibanaRequest, KibanaResponseFactory, IKibanaResponse, + IScopedClusterClient, + ElasticsearchClient, } from 'kibana/server'; import { DynamicSettings } from '../../common/runtime_types'; import { UMServerLibs } from '../lib/lib'; @@ -63,11 +64,8 @@ export type UMKibanaRouteWrapper = (uptimeRoute: UptimeRoute) => UMKibanaRoute; * This type can store custom parameters used by the internal Uptime route handlers. */ export interface UMRouteParams { - callES: ( - endpoint: string, - clientParams?: Record, - options?: LegacyCallAPIOptions | undefined - ) => Promise; + callES: ElasticsearchClient; + esClient: IScopedClusterClient; dynamicSettings: DynamicSettings; savedObjectsClient: SavedObjectsClientContract; } diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index 84a85a54afe138..b2f1c7d6424e62 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -13,11 +13,11 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], }, handler: async (context, request, response) => { - const { callAsCurrentUser: callES } = context.core.elasticsearch.legacy.client; + const { client: esClient } = context.core.elasticsearch; const { client: savedObjectsClient } = context.core.savedObjects; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); return uptimeRoute.handler( - { callES, savedObjectsClient, dynamicSettings }, + { callES: esClient.asCurrentUser, esClient, savedObjectsClient, dynamicSettings }, context, request, response diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 3bf697bd97b14f..1a7c0d52cad5ce 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -65,6 +65,7 @@ const onlyNotInCoverageTests = [ require.resolve('../test/reporting_api_integration/reporting_without_security.config.ts'), require.resolve('../test/security_solution_endpoint_api_int/config.ts'), require.resolve('../test/ingest_manager_api_integration/config.ts'), + require.resolve('../test/functional_vis_wizard/config.ts'), require.resolve('../test/saved_object_tagging/functional/config.ts'), require.resolve('../test/saved_object_tagging/api_integration/security_and_spaces/config.ts'), require.resolve('../test/saved_object_tagging/api_integration/tagging_api/config.ts'), diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts index 8fb89042e4a903..4058b71356280a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/execution_status.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { AlertExecutionStatusErrorReasons } from '../../../../../plugins/alerts/common'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -49,7 +50,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); - expect(executionStatus.error.reason).to.be('decrypt'); + expect(executionStatus.error.reason).to.be(AlertExecutionStatusErrorReasons.Decrypt); expect(executionStatus.error.message).to.be('Unable to decrypt attribute "apiKey"'); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index a5dff437283aec..dbf8eb162fca71 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -134,7 +134,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { validateInstanceEvent(event, `resolved instance: 'instance'`); break; case 'active-instance': - validateInstanceEvent(event, `active instance: 'instance'`); + validateInstanceEvent(event, `active instance: 'instance' in actionGroup: 'default'`); break; // this will get triggered as we add new event actions default: diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 563127e028a628..22034328e5275a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -226,6 +226,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr instanceA: { status: 'Active', muted: false, + actionGroupId: 'default', activeStartDate: actualInstances.instanceA.activeStartDate, }, instanceB: { @@ -235,6 +236,7 @@ export default function createGetAlertInstanceSummaryTests({ getService }: FtrPr instanceC: { status: 'Active', muted: true, + actionGroupId: 'default', activeStartDate: actualInstances.instanceC.activeStartDate, }, instanceD: { diff --git a/x-pack/test/api_integration/apis/security/basic_login.js b/x-pack/test/api_integration/apis/security/basic_login.js index 43ef8e6b81eac4..a3049814b8e52b 100644 --- a/x-pack/test/api_integration/apis/security/basic_login.js +++ b/x-pack/test/api_integration/apis/security/basic_login.js @@ -147,9 +147,9 @@ export default function ({ getService }) { 'authentication_type', ]); expect(apiResponse.body.username).to.be(validUsername); - expect(apiResponse.body.authentication_provider).to.eql('__http__'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'http', name: '__http__' }); expect(apiResponse.body.authentication_type).to.be('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); describe('with session cookie', () => { @@ -193,9 +193,9 @@ export default function ({ getService }) { 'authentication_type', ]); expect(apiResponse.body.username).to.be(validUsername); - expect(apiResponse.body.authentication_provider).to.eql('basic'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(apiResponse.body.authentication_type).to.be('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); it('should extend cookie on every successful non-system API call', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts new file mode 100644 index 00000000000000..0cd1c21447dfee --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -0,0 +1,508 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../plugins/security_solution/common/constants'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + createRule, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getAllSignals, + getSignalsByRuleIds, + getSimpleRule, + waitForSignalsToBePresent, +} from '../../utils'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +export const ID = 'BhbXBmkBR346wHgn4PeZ'; + +// 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('Generating signals from source indexes', () => { + beforeEach(async () => { + await deleteAllAlerts(es); + await createSignalsIndex(supertest); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest); + await deleteAllAlerts(es); + }); + + describe('Signals from audit beat are of the expected structure', () => { + beforeEach(async () => { + await esArchiver.load('auditbeat/hosts'); + }); + + afterEach(async () => { + await esArchiver.unload('auditbeat/hosts'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + ancestors: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + original_time: '2019-02-19T17:40:03.790Z', + original_event: { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + from: '1900-01-01T00:00:00.000Z', + query: `_id:${ID}`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + action: 'socket_closed', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + }); + }); + }); + + /** + * These are a set of tests for whenever someone sets up their source + * index to have a name and mapping clash against "signal" with a numeric value. + * You should see the "signal" name/clash being copied to "original_signal" + * underneath the signal object and no errors when they do have a clash. + */ + describe('Signals generated from name clashes', () => { + beforeEach(async () => { + await esArchiver.load('signals/numeric_name_clash'); + }); + + afterEach(async () => { + await esArchiver.unload('signals/numeric_name_clash'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + original_time: '2020-10-28T05:08:53.000Z', + original_signal: 1, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_name_clash'], + from: '1900-01-01T00:00:00.000Z', + query: `_id:1`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_name_clash', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + kind: 'signal', + }, + }); + }); + }); + + /** + * These are a set of tests for whenever someone sets up their source + * index to have a name and mapping clash against "signal" with an object value. + * You should see the "signal" object/clash being copied to "original_signal" underneath + * the signal object and no errors when they do have a clash. + */ + describe('Signals generated from name clashes', () => { + beforeEach(async () => { + await esArchiver.load('signals/object_clash'); + }); + + afterEach(async () => { + await esArchiver.unload('signals/object_clash'); + }); + + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits.length).greaterThan(0); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + expect(signalsOpen.hits.hits[0]._source.signal.rule.rule_id).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: '_id:1', + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + const signalsOpen = await getAllSignals(supertest); + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + expect(signalNoRule).eql({ + parents: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + ], + status: 'open', + depth: 1, + parent: { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + original_time: '2020-10-28T05:08:53.000Z', + original_signal: { + child_1: { + child_2: { + value: 'some_value', + }, + }, + }, + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + // create a 1 signal from 1 auditbeat record + const rule: CreateRulesSchema = { + ...getSimpleRule(), + index: ['signal_object_clash'], + from: '1900-01-01T00:00:00.000Z', + query: `_id:1`, + }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest, 1); + + // Run signals on top of that 1 signal which should create a single signal (on top of) a signal + const ruleForSignals: CreateRulesSchema = { + ...getSimpleRule(), + rule_id: 'signal-on-signal', + index: [`${DEFAULT_SIGNALS_INDEX}*`], + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + }; + await createRule(supertest, ruleForSignals); + await waitForSignalsToBePresent(supertest, 2); + + // Get our single signal on top of a signal + const signalsOpen = await getSignalsByRuleIds(supertest, ['signal-on-signal']); + + // remove rule to cut down on touch points for test changes when the rule format changes + const { rule: removedRule, ...signalNoRule } = signalsOpen.hits.hits[0]._source.signal; + + expect(signalNoRule).eql({ + parents: [ + { + rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + ancestors: [ + { + id: '1', + type: 'event', + index: 'signal_object_clash', + depth: 0, + }, + { + rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + ], + status: 'open', + depth: 2, + parent: { + rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it + id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + type: 'signal', + index: '.siem-signals-default-000001', + depth: 1, + }, + original_time: signalNoRule.original_time, // original_time will always be changing sine it's based on a signal created here, so skip testing it + original_event: { + kind: 'signal', + }, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 24b76853164f22..962ae53b1241f0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -22,6 +22,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_statuses')); + loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); loadTestFile(require.resolve('./import_rules')); loadTestFile(require.resolve('./read_rules')); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 05a0f73dd0dc4f..c5e417c710283f 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -143,6 +143,19 @@ export const getQuerySignalIds = (signalIds: SignalIds) => ({ }, }); +/** + * Given an array of ruleIds for a test this will get the signals + * created from that rule_id. + * @param ruleIds The rule_id to search for signals + */ +export const getQuerySignalsRuleId = (ruleIds: string[]) => ({ + query: { + terms: { + 'signal.rule.rule_id': ruleIds, + }, + }, +}); + export const setSignalStatus = ({ signalIds, status, @@ -834,6 +847,22 @@ export const getAllSignals = async ( return signalsOpen; }; +export const getSignalsByRuleIds = async ( + supertest: SuperTest, + ruleIds: string[] +): Promise< + SearchResponse<{ + signal: Signal; + }> +> => { + const { body: signalsOpen }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalsRuleId(ruleIds)) + .expect(200); + return signalsOpen; +}; + export const installPrePackagedRules = async ( supertest: SuperTest ): Promise => { diff --git a/x-pack/test/functional/apps/canvas/filters.ts b/x-pack/test/functional/apps/canvas/filters.ts new file mode 100644 index 00000000000000..a4bd806597a933 --- /dev/null +++ b/x-pack/test/functional/apps/canvas/filters.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function canvasFiltersTest({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['canvas', 'common']); + const find = getService('find'); + const esArchiver = getService('esArchiver'); + + describe('filters', function () { + // there is an issue with FF not properly clicking on workpad elements + this.tags('skipFirefox'); + + before(async () => { + await esArchiver.load('canvas/filter'); + // load test workpad + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-b5618217-56d2-47fa-b756-1be2306cda68/page/1', + }); + }); + + it('filter updates when dropdown is changed', async () => { + // wait for all our elements to load up + await retry.try(async () => { + const elements = await testSubjects.findAll( + 'canvasWorkpadPage > canvasWorkpadPageElementContent' + ); + expect(elements).to.have.length(3); + }); + + // Double check that the filter has the correct time range and default filter value + const startingMatchFilters = await PageObjects.canvas.getMatchFiltersFromDebug(); + expect(startingMatchFilters[0].value).to.equal('apm'); + expect(startingMatchFilters[0].column).to.equal('project'); + + // Change dropdown value + await testSubjects.selectValue('canvasDropdownFilter__select', 'beats'); + + await retry.try(async () => { + const matchFilters = await PageObjects.canvas.getMatchFiltersFromDebug(); + expect(matchFilters[0].value).to.equal('beats'); + expect(matchFilters[0].column).to.equal('project'); + }); + }); + + it('filter updates when time range is changed', async () => { + // wait for all our elements to load up + await retry.try(async () => { + const elements = await testSubjects.findAll( + 'canvasWorkpadPage > canvasWorkpadPageElementContent' + ); + expect(elements).to.have.length(3); + }); + + const startingTimeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(startingTimeFilters[0].column).to.equal('@timestamp'); + expect(new Date(startingTimeFilters[0].from).toDateString()).to.equal('Sun Oct 18 2020'); + expect(new Date(startingTimeFilters[0].to).toDateString()).to.equal('Sat Oct 24 2020'); + + await testSubjects.click('superDatePickerstartDatePopoverButton'); + await find.clickByCssSelector('.react-datepicker [aria-label="day-19"]', 20000); + + await retry.try(async () => { + const timeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(timeFilters[0].column).to.equal('@timestamp'); + expect(new Date(timeFilters[0].from).toDateString()).to.equal('Mon Oct 19 2020'); + expect(new Date(timeFilters[0].to).toDateString()).to.equal('Sat Oct 24 2020'); + }); + + await testSubjects.click('superDatePickerendDatePopoverButton'); + await find.clickByCssSelector('.react-datepicker [aria-label="day-23"]', 20000); + + await retry.try(async () => { + const timeFilters = await PageObjects.canvas.getTimeFiltersFromDebug(); + expect(timeFilters[0].column).to.equal('@timestamp'); + expect(new Date(timeFilters[0].from).toDateString()).to.equal('Mon Oct 19 2020'); + expect(new Date(timeFilters[0].to).toDateString()).to.equal('Fri Oct 23 2020'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/canvas/index.js b/x-pack/test/functional/apps/canvas/index.js index 7ee48beaabb2ae..36fbd812959249 100644 --- a/x-pack/test/functional/apps/canvas/index.js +++ b/x-pack/test/functional/apps/canvas/index.js @@ -22,6 +22,7 @@ export default function canvasApp({ loadTestFile, getService }) { this.tags('ciGroup2'); // CI requires tags ヽ(゜Q。)ノ? loadTestFile(require.resolve('./smoke_test')); loadTestFile(require.resolve('./expression')); + loadTestFile(require.resolve('./filters')); loadTestFile(require.resolve('./custom_elements')); loadTestFile(require.resolve('./feature_controls/canvas_security')); loadTestFile(require.resolve('./feature_controls/canvas_spaces')); diff --git a/x-pack/test/functional/apps/lens/persistent_context.ts b/x-pack/test/functional/apps/lens/persistent_context.ts index a115b720f6f2c2..467a33fb018546 100644 --- a/x-pack/test/functional/apps/lens/persistent_context.ts +++ b/x-pack/test/functional/apps/lens/persistent_context.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await filterBar.toggleFilterEnabled('ip'); await appsMenu.clickLink('Visualize', { category: 'kibana' }); await PageObjects.visualize.clickNewVisualization(); - await PageObjects.visualize.waitForVisualizationSelectPage(); + await PageObjects.visualize.waitForGroupsSelectPage(); await PageObjects.visualize.clickVisType('lens'); const timeRange = await PageObjects.timePicker.getTimeConfig(); expect(timeRange.start).to.equal('Sep 7, 2015 @ 06:31:44.000'); diff --git a/x-pack/test/functional/apps/maps/visualize_create_menu.js b/x-pack/test/functional/apps/maps/visualize_create_menu.js index ef39771d6be075..549901884d39b0 100644 --- a/x-pack/test/functional/apps/maps/visualize_create_menu.js +++ b/x-pack/test/functional/apps/maps/visualize_create_menu.js @@ -31,6 +31,7 @@ export default function ({ getService, getPageObjects }) { }); it('should not show legacy region map visualizion in create menu', async () => { + await PageObjects.visualize.clickAggBasedVisualizations(); const hasLegecyViz = await PageObjects.visualize.hasRegionMap(); expect(hasLegecyViz).to.equal(false); }); @@ -41,6 +42,7 @@ export default function ({ getService, getPageObjects }) { }); it('should take users to Maps application when Maps is clicked', async () => { + await PageObjects.visualize.goBackToGroups(); await PageObjects.visualize.clickMapsApp(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.maps.waitForLayersToLoad(); diff --git a/x-pack/test/functional/apps/security/rbac_phase1.js b/x-pack/test/functional/apps/security/rbac_phase1.js index b138859d01361c..58c72eaa3072ec 100644 --- a/x-pack/test/functional/apps/security/rbac_phase1.js +++ b/x-pack/test/functional/apps/security/rbac_phase1.js @@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }) { log.debug('log in as kibanauser with rbac_all role'); await PageObjects.security.login('kibanauser', 'changeme'); log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); log.debug('clickVerticalBarChart'); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch(); diff --git a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts index 07c7a1db1e72ae..b3a89b7df3d344 100644 --- a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts +++ b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('works in visualizations', () => { before(async () => { - await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.navigateToNewAggBasedVisualization(); await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch('histogram-test'); await PageObjects.visChart.waitForVisualization(); diff --git a/x-pack/test/functional/apps/visualize/reporting.ts b/x-pack/test/functional/apps/visualize/reporting.ts index 0752971ba832ba..71172dbdc14058 100644 --- a/x-pack/test/functional/apps/visualize/reporting.ts +++ b/x-pack/test/functional/apps/visualize/reporting.ts @@ -40,6 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Print PDF button', () => { it('is not available if new', async () => { await PageObjects.common.navigateToUrl('visualize', 'new', { useActualUrl: true }); + await PageObjects.visualize.clickAggBasedVisualizations(); await PageObjects.visualize.clickAreaChart(); await PageObjects.visualize.clickNewSearch('ecommerce'); await PageObjects.reporting.openPdfReportingPanel(); diff --git a/x-pack/test/functional/es_archives/canvas/filter/data.json.gz b/x-pack/test/functional/es_archives/canvas/filter/data.json.gz new file mode 100644 index 00000000000000..524a5c38fc3769 Binary files /dev/null and b/x-pack/test/functional/es_archives/canvas/filter/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/canvas/filter/mappings.json b/x-pack/test/functional/es_archives/canvas/filter/mappings.json new file mode 100644 index 00000000000000..1f7e4092d85751 --- /dev/null +++ b/x-pack/test/functional/es_archives/canvas/filter/mappings.json @@ -0,0 +1,2422 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "eaf6f5841dbf4cb5e3045860f75f53ca", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "477f214ff61acc3af26a7b7818e380c1", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "2b83397e3eaaaa8ef15e38813f3721c3", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "f74dfe498e1849267cda41580b2be110", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "43012c7ebc4cb57054e0a490e4b43023", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + }, + "lastExecutionDate": { + "type": "date" + }, + "status": { + "type": "keyword" + } + } + }, + "meta": { + "properties": { + "versionApiKeyLastmodified": { + "type": "keyword" + } + } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "ack_data": { + "type": "text" + }, + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_urls": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "canvas-workpad": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json b/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json new file mode 100644 index 00000000000000..ca2cf0de2d8452 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/numeric_name_clash/data.json @@ -0,0 +1,12 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "signal_name_clash", + "source": { + "@timestamp": "2020-10-28T05:08:53.000Z", + "signal": 1 + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json b/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json new file mode 100644 index 00000000000000..98823cb4b4250e --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/numeric_name_clash/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "signal_name_clash", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "signal": { "type": "keyword" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/es_archives/signals/object_clash/data.json b/x-pack/test/functional/es_archives/signals/object_clash/data.json new file mode 100644 index 00000000000000..d2f844312e8fb2 --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/object_clash/data.json @@ -0,0 +1,12 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "signal_object_clash", + "source": { + "@timestamp": "2020-10-28T05:08:53.000Z", + "signal": { "child_1": { "child_2": { "value": "some_value" } } } + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/signals/object_clash/mappings.json b/x-pack/test/functional/es_archives/signals/object_clash/mappings.json new file mode 100644 index 00000000000000..9297ff3e867c9a --- /dev/null +++ b/x-pack/test/functional/es_archives/signals/object_clash/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "signal_object_clash", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "signal": { "type": "object" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index 23b5057573b3b7..dcb22a9518936c 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -79,5 +79,27 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.missingOrFail('add-element-button'); }, + + async getTimeFiltersFromDebug() { + await testSubjects.existOrFail('canvasDebug__content'); + + const contentElem = await testSubjects.find('canvasDebug__content'); + const content = await contentElem.getVisibleText(); + + const filters = JSON.parse(content); + + return filters.and.filter((f: any) => f.filterType === 'time'); + }, + + async getMatchFiltersFromDebug() { + await testSubjects.existOrFail('canvasDebug__content'); + + const contentElem = await testSubjects.find('canvasDebug__content'); + const content = await contentElem.getVisibleText(); + + const filters = JSON.parse(content); + + return filters.and.filter((f: any) => f.filterType === 'exactly'); + }, }; } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index ffb74837e9fdd0..3eed3fc0f26ae7 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -222,8 +222,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, async editMissingValues(option: string) { await retry.try(async () => { - await testSubjects.click('lnsMissingValuesButton'); - await testSubjects.exists('lnsMissingValuesSelect'); + await testSubjects.click('lnsValuesButton'); + await testSubjects.exists('lnsValuesButton'); }); await testSubjects.click('lnsMissingValuesSelect'); const optionSelector = await find.byCssSelector(`#${option}`); diff --git a/x-pack/test/functional_vis_wizard/apps/index.ts b/x-pack/test/functional_vis_wizard/apps/index.ts new file mode 100644 index 00000000000000..730ef8e01fde9a --- /dev/null +++ b/x-pack/test/functional_vis_wizard/apps/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Visualization Wizard', function () { + this.tags('ciGroup4'); + + loadTestFile(require.resolve('./visualization_wizard')); + }); +} diff --git a/x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts b/x-pack/test/functional_vis_wizard/apps/visualization_wizard.ts new file mode 100644 index 00000000000000..422b3a0d10fd25 --- /dev/null +++ b/x-pack/test/functional_vis_wizard/apps/visualization_wizard.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['visualize']); + + describe('lens and maps disabled', function () { + before(async function () { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('visualize/default'); + }); + + after(async function () { + await esArchiver.unload('logstash_functional'); + await esArchiver.unload('visualize/default'); + }); + + it('should not display lens and maps cards', async function () { + await PageObjects.visualize.navigateToNewVisualization(); + const expectedChartTypes = ['Custom visualization', 'TSVB']; + + // find all the chart types and make sure that maps and lens cards are not there + const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); + expect(chartTypes).to.eql(expectedChartTypes); + }); + }); +} diff --git a/x-pack/test/functional_vis_wizard/config.ts b/x-pack/test/functional_vis_wizard/config.ts new file mode 100644 index 00000000000000..f5ffb6996e0460 --- /dev/null +++ b/x-pack/test/functional_vis_wizard/config.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * 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 xPackFunctionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + // default to the xpack functional config + ...xPackFunctionalConfig.getAll(), + testFiles: [require.resolve('./apps')], + junit: { + reportName: 'X-Pack Visualization Wizard Tests with Lens and Maps disabled', + }, + kbnTestServer: { + ...xPackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.lens.enabled=false', + '--xpack.maps.enabled=false', + ], + }, + }; +} diff --git a/x-pack/test/functional_vis_wizard/ftr_provider_context.d.ts b/x-pack/test/functional_vis_wizard/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..b80934b5c0e9da --- /dev/null +++ b/x-pack/test/functional_vis_wizard/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 '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { pageObjects }; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 4c97c8556d7dff..9e4006681dc8dd 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -392,21 +392,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(instancesList.map((instance) => omit(instance, 'duration'))).to.eql([ { instance: 'us-central', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-central']) .utc() .format('D MMM YYYY @ HH:mm:ss'), }, { instance: 'us-east', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-east']) .utc() .format('D MMM YYYY @ HH:mm:ss'), }, { instance: 'us-west', - status: 'Active', + status: 'Active (Default)', start: moment(dateOnAllInstancesFromApiResponse['us-west']) .utc() .format('D MMM YYYY @ HH:mm:ss'), diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts index 4ad9501f3936f6..a5f1aa8003f044 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_by_upload.ts @@ -92,7 +92,7 @@ export default function (providerContext: FtrProviderContext) { .send(buf) .expect(400); expect(res.error.text).to.equal( - '{"statusCode":400,"error":"Bad Request","message":"Uploaded archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."}' + '{"statusCode":400,"error":"Bad Request","message":"Archive seems empty. Assumed content type was application/gzip, check if this matches the archive type."}' ); }); @@ -105,7 +105,7 @@ export default function (providerContext: FtrProviderContext) { .send(buf) .expect(400); expect(res.error.text).to.equal( - '{"statusCode":400,"error":"Bad Request","message":"Error during extraction of uploaded package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."}' + '{"statusCode":400,"error":"Bad Request","message":"Error during extraction of package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."}' ); }); diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 459dc4739897c5..c31f6b689e972c 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -79,9 +79,9 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.eql('realm'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); describe('initiating SPNEGO', () => { @@ -146,7 +146,7 @@ export default function ({ getService }: FtrProviderContext) { enabled: true, authentication_realm: { name: 'kerb1', type: 'kerberos' }, lookup_realm: { name: 'kerb1', type: 'kerberos' }, - authentication_provider: 'kerberos', + authentication_provider: { type: 'kerberos', name: 'kerberos' }, authentication_type: 'token', }); }); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts index c2335cf04504fa..1fdb15a86ce0a2 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.ts @@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.be('realm'); // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud }); @@ -235,7 +235,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user1'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); @@ -289,7 +289,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user2'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index e4e194a619a954..7c408d8b903e32 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -156,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be('user1'); expect(apiResponse.body.authentication_realm).to.eql({ name: 'oidc1', type: 'oidc' }); - expect(apiResponse.body.authentication_provider).to.eql('oidc'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'oidc', name: 'oidc' }); expect(apiResponse.body.authentication_type).to.be('token'); }); }); diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts index 0559e9e96fe3f7..43b728d12311d8 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts @@ -93,8 +93,8 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); - // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); + // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); it('should properly set cookie and authenticate user', async () => { @@ -123,7 +123,7 @@ export default function ({ getService }: FtrProviderContext) { }, authentication_realm: { name: 'pki1', type: 'pki' }, lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: 'pki', + authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'token', }); @@ -168,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { }, authentication_realm: { name: 'pki1', type: 'pki' }, lookup_realm: { name: 'pki1', type: 'pki' }, - authentication_provider: 'pki', + authentication_provider: { name: 'pki', type: 'pki' }, authentication_type: 'token', }); diff --git a/x-pack/test/security_api_integration/session_idle.config.ts b/x-pack/test/security_api_integration/session_idle.config.ts index 34a23b7f5f9263..b8f9141c0a29e2 100644 --- a/x-pack/test/security_api_integration/session_idle.config.ts +++ b/x-pack/test/security_api_integration/session_idle.config.ts @@ -15,16 +15,35 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); + return { testFiles: [resolve(__dirname, './tests/session_idle')], services: { + randomness: kibanaAPITestsConfig.get('services.randomness'), legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, servers: xPackAPITestsConfig.get('servers'), - esTestCluster: xPackAPITestsConfig.get('esTestCluster'), + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + 'xpack.security.authc.realms.saml.saml1.order=0', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + ], + }, kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), @@ -32,6 +51,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.idleTimeout=5s', '--xpack.security.session.cleanupInterval=10s', + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0 } }, + saml: { + saml_fallback: { order: 1, realm: 'saml1' }, + saml_override: { order: 2, realm: 'saml1', session: { idleTimeout: '1m' } }, + saml_disable: { order: 3, realm: 'saml1', session: { idleTimeout: 0 } }, + }, + })}`, ], }, diff --git a/x-pack/test/security_api_integration/session_lifespan.config.ts b/x-pack/test/security_api_integration/session_lifespan.config.ts index b5fdf6b6914b14..4001a963bfae8f 100644 --- a/x-pack/test/security_api_integration/session_lifespan.config.ts +++ b/x-pack/test/security_api_integration/session_lifespan.config.ts @@ -15,16 +15,35 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ); const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); + const kibanaPort = xPackAPITestsConfig.get('servers.kibana.port'); + const idpPath = resolve(__dirname, './fixtures/saml/idp_metadata.xml'); + return { testFiles: [resolve(__dirname, './tests/session_lifespan')], services: { + randomness: kibanaAPITestsConfig.get('services.randomness'), legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'), }, servers: xPackAPITestsConfig.get('servers'), - esTestCluster: xPackAPITestsConfig.get('esTestCluster'), + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + 'xpack.security.authc.realms.saml.saml1.order=0', + `xpack.security.authc.realms.saml.saml1.idp.metadata.path=${idpPath}`, + 'xpack.security.authc.realms.saml.saml1.idp.entity_id=http://www.elastic.co/saml1', + `xpack.security.authc.realms.saml.saml1.sp.entity_id=http://localhost:${kibanaPort}`, + `xpack.security.authc.realms.saml.saml1.sp.logout=http://localhost:${kibanaPort}/logout`, + `xpack.security.authc.realms.saml.saml1.sp.acs=http://localhost:${kibanaPort}/api/security/saml/callback`, + 'xpack.security.authc.realms.saml.saml1.attributes.principal=urn:oid:0.0.7', + ], + }, kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), @@ -32,6 +51,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), '--xpack.security.session.lifespan=5s', '--xpack.security.session.cleanupInterval=10s', + `--xpack.security.authc.providers=${JSON.stringify({ + basic: { basic1: { order: 0 } }, + saml: { + saml_fallback: { order: 1, realm: 'saml1' }, + saml_override: { order: 2, realm: 'saml1', session: { lifespan: '1m' } }, + saml_disable: { order: 3, realm: 'saml1', session: { lifespan: 0 } }, + }, + })}`, ], }, diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 2881020f521eed..432fd6ff912806 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -10,6 +10,7 @@ import { resolve } from 'path'; import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; import { getStateAndNonce } from '../../../oidc_api_integration/fixtures/oidc_tools'; import { getMutualAuthenticationResponseToken, @@ -35,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { async function checkSessionCookie( sessionCookie: Cookie, username: string, - providerName: string, + provider: AuthenticationProvider, authenticationRealm: { name: string; type: string } | null, authenticationType: string ) { @@ -66,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { ]); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); if (authenticationRealm) { expect(apiResponse.body.authentication_realm).to.eql(authenticationRealm); } @@ -146,11 +147,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -182,11 +180,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -215,11 +210,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -244,7 +236,13 @@ export default function ({ getService }: FtrProviderContext) { )!; // Skip auth provider check since this comes from the reserved realm, // which is not available when running on ESS - await checkSessionCookie(basicSessionCookie, 'elastic', 'basic1', null, 'realm'); + await checkSessionCookie( + basicSessionCookie, + 'elastic', + { type: 'basic', name: 'basic1' }, + null, + 'realm' + ); const authenticationResponse = await supertest .post('/api/security/saml/callback') @@ -267,11 +265,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -293,11 +288,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml1SessionCookie, 'a@b.c', - 'saml1', - { - name: 'saml1', - type: 'saml', - }, + { type: 'saml', name: 'saml1' }, + { name: 'saml1', type: 'saml' }, 'token' ); @@ -321,11 +313,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -346,11 +335,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml1SessionCookie, 'a@b.c', - 'saml1', - { - name: 'saml1', - type: 'saml', - }, + { type: 'saml', name: 'saml1' }, + { name: 'saml1', type: 'saml' }, 'token' ); @@ -376,11 +362,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -466,11 +449,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'a@b.c', - providerName, - { - name: providerName, - type: 'saml', - }, + { type: 'saml', name: providerName }, + { name: providerName, type: 'saml' }, 'token' ); } @@ -537,11 +517,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( saml2SessionCookie, 'a@b.c', - 'saml2', - { - name: 'saml2', - type: 'saml', - }, + { type: 'saml', name: 'saml2' }, + { name: 'saml2', type: 'saml' }, 'token' ); }); @@ -586,11 +563,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'tester@TEST.ELASTIC.CO', - 'kerberos1', - { - name: 'kerb1', - type: 'kerberos', - }, + { type: 'kerberos', name: 'kerberos1' }, + { name: 'kerb1', type: 'kerberos' }, 'token' ); }); @@ -635,11 +609,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'tester@TEST.ELASTIC.CO', - 'kerberos1', - { - name: 'kerb1', - type: 'kerberos', - }, + { type: 'kerberos', name: 'kerberos1' }, + { name: 'kerb1', type: 'kerberos' }, 'token' ); }); @@ -677,11 +648,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'user2', - 'oidc1', - { - name: 'oidc1', - type: 'oidc', - }, + { type: 'oidc', name: 'oidc1' }, + { name: 'oidc1', type: 'oidc' }, 'token' ); }); @@ -737,11 +705,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'user1', - 'oidc1', - { - name: 'oidc1', - type: 'oidc', - }, + { type: 'oidc', name: 'oidc1' }, + { name: 'oidc1', type: 'oidc' }, 'token' ); }); @@ -779,11 +744,8 @@ export default function ({ getService }: FtrProviderContext) { await checkSessionCookie( request.cookie(cookies[0])!, 'first_client', - 'pki1', - { - name: 'pki1', - type: 'pki', - }, + { type: 'pki', name: 'pki1' }, + { name: 'pki1', type: 'pki' }, 'token' ); }); diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index 8770d87c0cf8ce..030c6f91d2aedc 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -65,7 +65,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.username).to.be(username); expect(apiResponse.body.authentication_realm).to.eql({ name: 'saml1', type: 'saml' }); - expect(apiResponse.body.authentication_provider).to.eql('saml'); + expect(apiResponse.body.authentication_provider).to.eql({ type: 'saml', name: 'saml' }); expect(apiResponse.body.authentication_type).to.be('token'); } @@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(user.username).to.eql(username); - expect(user.authentication_provider).to.eql('basic'); + expect(user.authentication_provider).to.eql({ type: 'basic', name: 'basic' }); expect(user.authentication_type).to.be('realm'); // Do not assert on the `authentication_realm`, as the value differes for on-prem vs cloud }); diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index 703180442f8f5d..c1e8bb9938986a 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -7,6 +7,8 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -14,9 +16,15 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const config = getService('config'); const log = getService('log'); - const [username, password] = config.get('servers.elasticsearch.auth').split(':'); - - async function checkSessionCookie(sessionCookie: Cookie, providerName: string) { + const randomness = getService('randomness'); + const [basicUsername, basicPassword] = config.get('servers.elasticsearch.auth').split(':'); + const kibanaServerConfig = config.get('servers.kibana'); + + async function checkSessionCookie( + sessionCookie: Cookie, + username: string, + provider: AuthenticationProvider + ) { const apiResponse = await supertest .get('/internal/security/me') .set('kbn-xsrf', 'xxx') @@ -24,9 +32,11 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); - return request.cookie(apiResponse.headers['set-cookie'][0])!; + return Array.isArray(apiResponse.headers['set-cookie']) + ? request.cookie(apiResponse.headers['set-cookie'][0])! + : undefined; } async function getNumberOfSessionDocuments() { @@ -35,6 +45,31 @@ export default function ({ getService }: FtrProviderContext) { }).value; } + async function loginWithSAML(providerName: string) { + const handshakeResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName, currentURL: '' }) + .expect(200); + + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .set('kbn-xsrf', 'xxx') + .set('Cookie', request.cookie(handshakeResponse.headers['set-cookie'][0])!.cookieString()) + .send({ + SAMLResponse: await getSAMLResponse({ + destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`, + sessionIndex: String(randomness.naturalNumber()), + inResponseTo: await getSAMLRequestId(handshakeResponse.body.location), + }), + }) + .expect(302); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, 'a@b.c', { type: 'saml', name: providerName }); + return cookie; + } + describe('Session Idle cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', waitForStatus: 'green' }); @@ -52,14 +87,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); const sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { type: 'basic', name: 'basic1' }); expect(await getNumberOfSessionDocuments()).to.be(1); // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s @@ -76,6 +111,66 @@ export default function ({ getService }: FtrProviderContext) { .expect(401); }); + it('should properly clean up session expired because of idle timeout when providers override global session config', async function () { + this.timeout(60000); + + const [ + samlDisableSessionCookie, + samlOverrideSessionCookie, + samlFallbackSessionCookie, + ] = await Promise.all([ + loginWithSAML('saml_disable'), + loginWithSAML('saml_override'), + loginWithSAML('saml_fallback'), + ]); + + const response = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: { username: basicUsername, password: basicPassword }, + }) + .expect(200); + + const basicSessionCookie = request.cookie(response.headers['set-cookie'][0])!; + await checkSessionCookie(basicSessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); + expect(await getNumberOfSessionDocuments()).to.be(4); + + // Cleanup routine runs every 10s, and idle timeout threshold is three times larger than 5s + // idle timeout, let's wait for 30s to make sure cleanup routine runs when idle timeout + // threshold is exceeded. + await delay(30000); + + // Session for basic and SAML that used global session settings should not be valid anymore. + expect(await getNumberOfSessionDocuments()).to.be(2); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlFallbackSessionCookie.cookieString()) + .expect(401); + + // But sessions for the SAML with overridden and disabled lifespan should still be valid. + await checkSessionCookie(samlOverrideSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_override', + }); + await checkSessionCookie(samlDisableSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_disable', + }); + }); + it('should not clean up session if user is active', async function () { this.timeout(60000); @@ -84,14 +179,14 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); let sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { type: 'basic', name: 'basic1' }); expect(await getNumberOfSessionDocuments()).to.be(1); // Run 20 consequent requests with 1.5s delay, during this time cleanup procedure should run at @@ -100,7 +195,10 @@ export default function ({ getService }: FtrProviderContext) { // Session idle timeout is 15s, let's wait 10s and make a new request that would extend the session. await delay(1500); - sessionCookie = await checkSessionCookie(sessionCookie, 'basic'); + sessionCookie = (await checkSessionCookie(sessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }))!; log.debug(`Session is still valid after ${(counter + 1) * 1.5}s`); } diff --git a/x-pack/test/security_api_integration/tests/session_idle/extension.ts b/x-pack/test/security_api_integration/tests/session_idle/extension.ts index 64ecdda2013010..16945184c0f6f5 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/extension.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/extension.ts @@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', params: { username: validUsername, password: validPassword }, }) @@ -61,7 +61,7 @@ export default function ({ getService }: FtrProviderContext) { expect(body.now).to.be.a('number'); expect(body.idleTimeoutExpiration).to.be.a('number'); expect(body.lifespanExpiration).to.be(null); - expect(body.provider).to.eql({ type: 'basic', name: 'basic' }); + expect(body.provider).to.eql({ type: 'basic', name: 'basic1' }); }); it('should not extend the session', async () => { diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 8b136e540f13fb..59e8c746a6d077 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -7,15 +7,23 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('legacyEs'); const config = getService('config'); - const [username, password] = config.get('servers.elasticsearch.auth').split(':'); + const randomness = getService('randomness'); + const [basicUsername, basicPassword] = config.get('servers.elasticsearch.auth').split(':'); + const kibanaServerConfig = config.get('servers.kibana'); - async function checkSessionCookie(sessionCookie: Cookie, providerName: string) { + async function checkSessionCookie( + sessionCookie: Cookie, + username: string, + provider: AuthenticationProvider + ) { const apiResponse = await supertest .get('/internal/security/me') .set('kbn-xsrf', 'xxx') @@ -23,7 +31,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(apiResponse.body.username).to.be(username); - expect(apiResponse.body.authentication_provider).to.be(providerName); + expect(apiResponse.body.authentication_provider).to.eql(provider); } async function getNumberOfSessionDocuments() { @@ -32,6 +40,31 @@ export default function ({ getService }: FtrProviderContext) { }).value; } + async function loginWithSAML(providerName: string) { + const handshakeResponse = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ providerType: 'saml', providerName, currentURL: '' }) + .expect(200); + + const authenticationResponse = await supertest + .post('/api/security/saml/callback') + .set('kbn-xsrf', 'xxx') + .set('Cookie', request.cookie(handshakeResponse.headers['set-cookie'][0])!.cookieString()) + .send({ + SAMLResponse: await getSAMLResponse({ + destination: `http://localhost:${kibanaServerConfig.port}/api/security/saml/callback`, + sessionIndex: String(randomness.naturalNumber()), + inResponseTo: await getSAMLRequestId(handshakeResponse.body.location), + }), + }) + .expect(302); + + const cookie = request.cookie(authenticationResponse.headers['set-cookie'][0])!; + await checkSessionCookie(cookie, 'a@b.c', { type: 'saml', name: providerName }); + return cookie; + } + describe('Session Lifespan cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', waitForStatus: 'green' }); @@ -49,14 +82,17 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ providerType: 'basic', - providerName: 'basic', + providerName: 'basic1', currentURL: '/', - params: { username, password }, + params: { username: basicUsername, password: basicPassword }, }) .expect(200); const sessionCookie = request.cookie(response.headers['set-cookie'][0])!; - await checkSessionCookie(sessionCookie, 'basic'); + await checkSessionCookie(sessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); expect(await getNumberOfSessionDocuments()).to.be(1); // Cleanup routine runs every 10s, let's wait for 30s to make sure it runs multiple times and @@ -71,5 +107,63 @@ export default function ({ getService }: FtrProviderContext) { .set('Cookie', sessionCookie.cookieString()) .expect(401); }); + + it('should properly clean up session expired because of lifespan when providers override global session config', async function () { + this.timeout(60000); + + const [ + samlDisableSessionCookie, + samlOverrideSessionCookie, + samlFallbackSessionCookie, + ] = await Promise.all([ + loginWithSAML('saml_disable'), + loginWithSAML('saml_override'), + loginWithSAML('saml_fallback'), + ]); + + const response = await supertest + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic1', + currentURL: '/', + params: { username: basicUsername, password: basicPassword }, + }) + .expect(200); + const basicSessionCookie = request.cookie(response.headers['set-cookie'][0])!; + await checkSessionCookie(basicSessionCookie, basicUsername, { + type: 'basic', + name: 'basic1', + }); + expect(await getNumberOfSessionDocuments()).to.be(4); + + // Cleanup routine runs every 10s, let's wait for 30s to make sure it runs multiple times and + // when lifespan is exceeded. + await delay(30000); + + // Session for basic and SAML that used global session settings should not be valid anymore. + expect(await getNumberOfSessionDocuments()).to.be(2); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', basicSessionCookie.cookieString()) + .expect(401); + await supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', samlFallbackSessionCookie.cookieString()) + .expect(401); + + // But sessions for the SAML with overridden and disabled lifespan should still be valid. + await checkSessionCookie(samlOverrideSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_override', + }); + await checkSessionCookie(samlDisableSessionCookie, 'a@b.c', { + type: 'saml', + name: 'saml_disable', + }); + }); }); } diff --git a/yarn.lock b/yarn.lock index b79e246b27851b..0b429c96c18479 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3399,11 +3399,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@sindresorhus/is@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" @@ -6461,14 +6456,6 @@ aggregate-error@2.1.0: clean-stack "^2.0.0" indent-string "^3.0.0" -aggregate-error@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-1.0.0.tgz#888344dad0220a72e3af50906117f48771925fac" - integrity sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w= - dependencies: - clean-stack "^1.0.0" - indent-string "^3.0.0" - aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" @@ -6620,13 +6607,6 @@ angular@>=1.0.6, angular@^1.8.0: resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.0.tgz#b1ec179887869215cab6dfd0df2e42caa65b1b51" integrity sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg== -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -6717,7 +6697,7 @@ ansi-styles@^2.0.1, ansi-styles@^2.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.0.0, ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -6749,11 +6729,6 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= -ansi@^0.3.0, ansi@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" - integrity sha1-DELU+xcWDVqa8eSEus4cZpIsGyE= - ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -7154,11 +7129,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= - array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" @@ -7259,7 +7229,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-uniq@^1.0.0, array-uniq@^1.0.1: +array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= @@ -7486,7 +7456,7 @@ async@^1.4.2, async@^1.5.2, async@~1.5.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.0, async@^2.1.4, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: +async@^2.1.4, async@^2.6.0, async@^2.6.1, async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -8228,23 +8198,6 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bin-version-check@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-3.0.0.tgz#e24ebfa6b63cb0387c5fc174f86e5cc812ca7cc9" - integrity sha1-4k6/prY8sDh8X8F0+G5cyBLKfMk= - dependencies: - bin-version "^2.0.0" - semver "^5.1.0" - semver-truncate "^1.0.0" - -bin-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-2.0.0.tgz#2cc95d83b522bdef2e99978e76aeb5491c8114ff" - integrity sha1-LMldg7Uive8umZeOdq61SRyBFP8= - dependencies: - execa "^0.1.1" - find-versions "^2.0.0" - binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" @@ -8260,11 +8213,6 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -binaryextensions@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935" - integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA== - bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -8391,19 +8339,6 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== -boxen@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" - boxen@^4.1.0, boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -8930,19 +8865,6 @@ cacheable-lookup@^2.0.0: "@types/keyv" "^3.1.1" keyv "^4.0.0" -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -9083,11 +9005,6 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -9292,11 +9209,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.1.tgz#942835f750e4ec61a308e60c2ef8cc1011202efc" integrity sha1-lCg191Dk7GGjCOYMLvjMEBEgLvw= -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -9466,11 +9378,6 @@ chromedriver@^86.0.0: mkdirp "^1.0.4" tcp-port-used "^1.0.1" -ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" - integrity sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -9489,13 +9396,6 @@ circular-json@^0.3.1: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== -class-extend@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/class-extend/-/class-extend-0.1.2.tgz#8057a82b00f53f82a5d62c50ef8cffdec6fabc34" - integrity sha1-gFeoKwD1P4Kl1ixQ74z/3sb6vDQ= - dependencies: - object-assign "^2.0.0" - class-utils@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" @@ -9519,7 +9419,7 @@ clean-css@4.2.x, clean-css@^4.2.3: dependencies: source-map "~0.6.0" -clean-stack@^1.0.0, clean-stack@^1.3.0: +clean-stack@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" integrity sha1-noIVAa6XmYbEax1m0tQy2y/UrjE= @@ -9537,11 +9437,6 @@ clean-webpack-plugin@^3.0.0: "@types/webpack" "^4.4.31" del "^4.1.1" -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= - cli-boxes@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" @@ -9568,11 +9463,6 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-list@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/cli-list/-/cli-list-0.2.0.tgz#7e673ee0dd39a611a486476e53f3c6b3941cb582" - integrity sha1-fmc+4N05phGkhkduU/PGs5QctYI= - cli-spinners@^2.0.0, cli-spinners@^2.2.0, cli-spinners@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" @@ -9719,32 +9609,19 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.0.tgz#eae0a2413f55c0942f818c229fefce845d7f3b1c" - integrity sha1-6uCiQT9VwJQvgYwin+/OhF1/Oxw= - dependencies: - is-regexp "^1.0.0" - is-supported-regexp-flag "^1.0.0" - -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= dependencies: mimic-response "^1.0.0" -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= - clone-stats@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^1.0.0, clone@^1.0.2, clone@^1.0.4: +clone@^1.0.2, clone@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= @@ -10093,18 +9970,7 @@ concat-stream@~2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -conf@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/conf/-/conf-1.4.0.tgz#1ea66c9d7a9b601674a5bb9d2b8dc3c726625e67" - integrity sha512-bzlVWS2THbMetHqXKB8ypsXN4DQ/1qopGwNJi1eYbpwesJcd86FBjFciCQX/YwAhp9bM7NVnPFqZ5LpV7gP0Dg== - dependencies: - dot-prop "^4.1.0" - env-paths "^1.0.0" - make-dir "^1.0.0" - pkg-up "^2.0.0" - write-file-atomic "^2.3.0" - -config-chain@^1.1.11, config-chain@^1.1.12, config-chain@~1.1.8: +config-chain@^1.1.12, config-chain@~1.1.8: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -10126,18 +9992,6 @@ configstore@^1.0.0: write-file-atomic "^1.1.2" xdg-basedir "^2.0.0" -configstore@^3.0.0, configstore@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -10445,7 +10299,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.0, create-error-class@^3.0.1: +create-error-class@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= @@ -10516,25 +10370,6 @@ cross-fetch@2.2.2: node-fetch "2.1.2" whatwg-fetch "2.0.4" -cross-spawn-async@^2.1.1: - version "2.2.5" - resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" - integrity sha1-hF/wwINKPe2dFg2sptOQkGuyiMw= - dependencies: - lru-cache "^4.0.0" - which "^1.2.8" - -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@7.0.1, cross-spawn@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -10552,12 +10387,14 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: - lru-cache "^4.0.1" + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" shebang-command "^1.2.0" which "^1.2.9" @@ -11221,11 +11058,6 @@ damerau-levenshtein@^1.0.4: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ= -dargs@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829" - integrity sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk= - dash-ast@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" @@ -11279,11 +11111,6 @@ dateformat@^1.0.11, dateformat@~1.0.12: get-stdin "^4.0.1" meow "^3.3.0" -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - dateformat@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -11298,7 +11125,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -11361,7 +11188,7 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -decompress-response@^3.2.0, decompress-response@^3.3.0: +decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= @@ -11438,11 +11265,6 @@ deep-equal@~1.0.1: resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= -deep-extend@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" - integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8= - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -11495,11 +11317,6 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -default-uid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-uid/-/default-uid-1.0.0.tgz#fcefa9df9f5ac40c8916d912dd1fe1146aa3c59e" - integrity sha1-/O+p359axAyJFtkS3R/hFGqjxZ4= - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -11703,11 +11520,6 @@ detab@2.0.3, detab@^2.0.0: dependencies: repeat-string "^1.5.4" -detect-conflict@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e" - integrity sha1-CIZXpmqWHAUBnbfEIwiDsca0F24= - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -11898,11 +11710,6 @@ diff@^1.3.2: resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= -diff@^2.1.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" - integrity sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k= - diff@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" @@ -12103,13 +11910,6 @@ dot-case@^3.0.3: no-case "^3.0.3" tslib "^1.10.0" -dot-prop@^4.1.0, dot-prop@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== - dependencies: - is-obj "^1.0.0" - dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -12153,14 +11953,6 @@ dotignore@^0.1.2: dependencies: minimatch "^3.0.4" -downgrade-root@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/downgrade-root/-/downgrade-root-1.2.2.tgz#531319715b0e81ffcc22eb28478ba27643e12c6c" - integrity sha1-UxMZcVsOgf/MIusoR4uidkPhLGw= - dependencies: - default-uid "^1.0.0" - is-root "^1.0.0" - dtrace-provider@~0.8: version "0.8.8" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" @@ -12203,14 +11995,6 @@ duration@^0.2.0: d "1" es5-ext "~0.10.46" -each-async@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" - integrity sha1-3uUim98KtrogEqOV4bhpq/iBNHM= - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" - each-props@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" @@ -12275,11 +12059,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^2.3.1: - version "2.7.4" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" - integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - ejs@^3.0.1, ejs@^3.1.2, ejs@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.5.tgz#aed723844dc20acb4b170cd9ab1017e476a0d93b" @@ -12445,7 +12224,7 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -encodeurl@^1.0.2, encodeurl@~1.0.2: +encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= @@ -12466,16 +12245,7 @@ endent@^2.0.1: fast-json-parse "^1.0.3" objectorarray "^1.0.4" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - -enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0, enhanced-resolve@^4.3.0: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== @@ -12503,11 +12273,6 @@ entities@~2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= - env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -12629,7 +12394,7 @@ error-stack-parser@^2.0.4, error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -error@^7.0.0, error@^7.0.2: +error@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI= @@ -13336,53 +13101,6 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== -execa@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.1.1.tgz#b09c2a9309bc0ef0501479472db3180f8d4c3edd" - integrity sha1-sJwqkwm8DvBQFHlHLbMYD41MPt0= - dependencies: - cross-spawn-async "^2.1.1" - object-assign "^4.0.1" - strip-eof "^1.0.0" - -execa@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.4.0.tgz#4eb6467a36a095fabb2970ff9d5e3fb7bce6ebc3" - integrity sha1-TrZGejaglfq7KXD/nV4/t7zm68M= - dependencies: - cross-spawn-async "^2.1.1" - is-stream "^1.1.0" - npm-run-path "^1.0.0" - object-assign "^4.0.1" - path-key "^1.0.0" - strip-eof "^1.0.0" - -execa@^0.6.0: - version "0.6.3" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" - integrity sha1-V7aaWU8IF1nGnlNw8NF7nLEWWP4= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -13411,13 +13129,6 @@ execa@^4.0.0, execa@^4.0.2: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execall@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73" - integrity sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M= - dependencies: - clone-regexp "^1.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -13555,24 +13266,6 @@ extend@^3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-1.1.1.tgz#12d7b0db850f7ff7e7081baf4005700060c4600b" - integrity sha1-Etew24UPf/fnCBuvQAVwAGDEYAs= - dependencies: - extend "^3.0.0" - spawn-sync "^1.0.15" - tmp "^0.0.29" - -external-editor@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -13966,11 +13659,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= - finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -14045,14 +13733,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-2.0.0.tgz#2ad90d490f6828c1aa40292cf709ac3318210c3c" - integrity sha1-KtkNSQ9oKMGqQCks9wmsMxghDDw= - dependencies: - array-uniq "^1.0.0" - semver-regex "^1.0.0" - find@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find/-/find-0.3.0.tgz#4082e8fc8d8320f1a382b5e4f521b9bc50775cb8" @@ -14060,16 +13740,6 @@ find@^0.3.0: dependencies: traverse-chain "~0.1.0" -findup-sync@3.0.0, findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -14080,6 +13750,16 @@ findup-sync@^2.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + findup-sync@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" @@ -14098,13 +13778,6 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -first-chunk-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70" - integrity sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA= - dependencies: - readable-stream "^2.0.2" - flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" @@ -14403,7 +14076,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0, from2@^2.1.1: +from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -14556,19 +14229,6 @@ fsu@^1.0.2: resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" integrity sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A== -fullname@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/fullname/-/fullname-3.3.0.tgz#a08747d6921229610b8178b7614fce10cb185f5a" - integrity sha1-oIdH1pISKWELgXi3YU/OEMsYX1o= - dependencies: - execa "^0.6.0" - filter-obj "^1.1.0" - mem "^1.1.0" - p-any "^1.0.0" - p-try "^1.0.0" - passwd-user "^2.1.0" - rc "^1.1.6" - function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -14598,17 +14258,6 @@ fuse.js@^3.6.1: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== -gauge@~1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93" - integrity sha1-6c7FSD09TuDvRLYKfZnkk14TbZM= - dependencies: - ansi "^0.3.0" - has-unicode "^2.0.0" - lodash.pad "^4.1.0" - lodash.padend "^4.1.0" - lodash.padstart "^4.1.0" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -14731,11 +14380,6 @@ get-stdin@^6.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -14779,14 +14423,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gh-got@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gh-got/-/gh-got-5.0.0.tgz#ee95be37106fd8748a96f8d1db4baea89e1bfa8a" - integrity sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo= - dependencies: - got "^6.2.0" - is-plain-obj "^1.1.0" - gherkin@^5.0.0, gherkin@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5" @@ -14827,13 +14463,6 @@ github-slugger@^1.0.0: dependencies: emoji-regex ">=6.0.0 <=6.1.1" -github-username@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/github-username/-/github-username-3.0.0.tgz#0a772219b3130743429f2456d0bdd3db55dce7b1" - integrity sha1-CnciGbMTB0NCnyRW0L3T21Xc57E= - dependencies: - gh-got "^5.0.0" - gl-matrix@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" @@ -14992,13 +14621,6 @@ glob@~7.0.0: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= - dependencies: - ini "^1.3.4" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -15006,7 +14628,7 @@ global-dirs@^2.0.1: dependencies: ini "^1.3.5" -global-modules@2.0.0: +global-modules@2.0.0, global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== @@ -15042,16 +14664,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global-tunnel-ng@^2.5.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" - integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== - dependencies: - encodeurl "^1.0.2" - lodash "^4.17.10" - npm-conf "^1.1.3" - tunnel "^0.0.6" - global@^4.3.1, global@^4.3.2, global@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -15131,18 +14743,6 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-4.1.0.tgz#080f54549ec1b82a6c60e631fc82e1211dbe95f8" - integrity sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg= - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^6.0.1" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -15276,66 +14876,6 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" -got@^6.2.0, got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -got@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -got@^8.3.1, got@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -15602,13 +15142,6 @@ grid-index@^1.1.0: resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== -grouped-queue@^0.3.0, grouped-queue@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/grouped-queue/-/grouped-queue-0.3.3.tgz#c167d2a5319c5a0e0964ef6a25b7c2df8996c85c" - integrity sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw= - dependencies: - lodash "^4.17.2" - growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -15894,11 +15427,6 @@ has-color@~0.1.0: resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8= -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -15921,23 +15449,11 @@ has-glob@^1.0.0: dependencies: is-glob "^3.0.0" -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== - has-symbols@^1.0.0, has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== - dependencies: - has-symbol-support-x "^1.4.1" - has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -16385,11 +15901,6 @@ htmlparser2@~3.3.0: domutils "1.1" readable-stream "1.0" -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -16518,13 +16029,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -humanize-string@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/humanize-string/-/humanize-string-1.0.2.tgz#fef0a8bc9b1b857ca4013bbfaea75071736988f6" - integrity sha512-PH5GBkXqFxw5+4eKaKRIkD23y6vRd/IXSl7IldyJxEXpDH9SEIXRORkBtkGni/ae2P7RVOw6Wxypd2tGXhha1w== - dependencies: - decamelize "^1.0.0" - hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -16547,7 +16051,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -16682,7 +16186,7 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= -import-local@2.0.0, import-local@^2.0.0: +import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== @@ -16803,7 +16307,7 @@ inline-style@^2.0.0: dependencies: dashify "^0.1.0" -inquirer@6.2.2, inquirer@^6.0.0: +inquirer@6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== @@ -16860,45 +16364,6 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@^1.0.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-1.2.3.tgz#4dec6f32f37ef7bb0b2ed3f1d1a5c3f545074918" - integrity sha1-TexvMvN+97sLLtPx0aXD9UUHSRg= - dependencies: - ansi-escapes "^1.1.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - external-editor "^1.1.0" - figures "^1.3.5" - lodash "^4.3.0" - mute-stream "0.0.6" - pinkie-promise "^2.0.0" - run-async "^2.2.0" - rx "^4.1.0" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" - integrity sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.1.0" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^5.5.2" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - inquirer@^7.0.0, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -16934,21 +16399,6 @@ insert-module-globals@^7.0.0: undeclared-identifiers "^1.1.2" xtend "^4.0.0" -insight@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/insight/-/insight-0.10.1.tgz#a0ecf668484a95d66e9be59644964e719cc83380" - integrity sha512-kLGeYQkh18f8KuC68QKdi0iwUcIaayJVB/STpX7x452/7pAUm1yfG4giJwcxbrTh0zNYtc8kBR+6maLMOzglOQ== - dependencies: - async "^2.1.4" - chalk "^2.3.0" - conf "^1.3.1" - inquirer "^5.0.0" - lodash.debounce "^4.0.8" - os-name "^2.0.1" - request "^2.74.0" - tough-cookie "^2.0.0" - uuid "^3.0.0" - install-artifact-from-github@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.0.2.tgz#e1e478dd29880b9112ecd684a84029603e234a9d" @@ -16971,10 +16421,10 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" -interpret@1.2.0, interpret@^1.0.0, interpret@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +interpret@^1.0.0, interpret@^1.1.0, interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== interpret@^2.0.0: version "2.2.0" @@ -17010,14 +16460,6 @@ intl@^1.2.5: resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" integrity sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94= -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - invariant@2.2.4, invariant@^2.1.0, invariant@^2.1.1, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -17030,11 +16472,6 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - io-ts@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.5.tgz#e6e3db9df8b047f9cbd6b69e7d2ad3e6437a0b13" @@ -17169,13 +16606,6 @@ is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" - integrity sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg== - dependencies: - ci-info "^1.0.0" - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -17230,11 +16660,6 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-docker@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-1.1.0.tgz#f04374d4eee5310e9a8e113bf1495411e46176a1" - integrity sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE= - is-docker@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" @@ -17347,14 +16772,6 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" integrity sha1-bghLvJIGH7sJcexYts5tQE4k2mk= -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - is-installed-globally@^0.3.1, is-installed-globally@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" @@ -17465,7 +16882,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0, is-obj@^1.0.1: +is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= @@ -17615,7 +17032,7 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: +is-retry-allowed@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= @@ -17625,18 +17042,6 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-root@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" - integrity sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU= - -is-scoped@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-scoped/-/is-scoped-1.0.0.tgz#449ca98299e713038256289ecb2b540dc437cb30" - integrity sha1-RJypgpnnEwOCViieyytUDcQ3yzA= - dependencies: - scoped-regex "^1.0.0" - is-secret@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/is-secret/-/is-secret-1.2.1.tgz#04b9ca1880ea763049606cfe6c2a08a93f33abe3" @@ -17674,11 +17079,6 @@ is-subset@^0.1.1: resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= -is-supported-regexp-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz#8b520c85fae7a253382d4b02652e045576e13bb8" - integrity sha1-i1IMhfrnolM4LUsCZS4EVXbhO7g= - is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -17949,23 +17349,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -istextorbinary@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.2.1.tgz#a5231a08ef6dd22b268d0895084cf8d58b5bec53" - integrity sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw== - dependencies: - binaryextensions "2" - editions "^1.3.3" - textextensions "2" - -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - iterall@^1.1.3, iterall@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" @@ -19057,7 +18440,7 @@ keymirror@0.1.1: resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" integrity sha1-kYiJ6hP40KQufFVyUO7nE63JXDU= -keyv@3.0.0, keyv@^3.0.0: +keyv@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== @@ -19173,13 +18556,6 @@ latest-version@^1.0.0: dependencies: package-json "^1.0.0" -latest-version@^3.0.0, latest-version@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -19234,13 +18610,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - lead@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" @@ -19484,16 +18853,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - load-json-file@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" @@ -19536,6 +18895,15 @@ loader-utils@2.0.0, loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -19566,11 +18934,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -locutus@^2.0.5: - version "2.0.10" - resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" - integrity sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ== - lodash-es@^4.17.11: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" @@ -19756,21 +19119,6 @@ lodash.once@^4.0.0, lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.pad@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70" - integrity sha1-QzCUmoM6fI2iLMIPaibE1Z3runA= - -lodash.padend@^4.1.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" - integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= - -lodash.padstart@^4.1.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b" - integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs= - lodash.partialright@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.partialright/-/lodash.partialright-4.2.1.tgz#0130d80e83363264d40074f329b8a3e7a8a1cc4b" @@ -19841,7 +19189,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19868,7 +19216,7 @@ log-symbols@3.0.0, log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^1.0.1, log-symbols@^1.0.2: +log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= @@ -19971,11 +19319,6 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -20035,11 +19378,6 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -macos-release@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-1.1.0.tgz#831945e29365b470aa8724b0ab36c8f8959d10fb" - integrity sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA== - macos-release@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.2.0.tgz#ab58d55dd4714f0a05ad4b0e90f4370fef5cdea8" @@ -20070,13 +19408,6 @@ magic-string@0.25.1: dependencies: sourcemap-codec "^1.4.1" -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== - dependencies: - pify "^3.0.0" - make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -20106,13 +19437,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74" - integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -20346,47 +19670,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem-fs-editor@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz#dd0a6eaf2bb8a6b37740067aa549eb530105af9f" - integrity sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58= - dependencies: - commondir "^1.0.1" - deep-extend "^0.4.0" - ejs "^2.3.1" - glob "^7.0.3" - globby "^6.1.0" - mkdirp "^0.5.0" - multimatch "^2.0.0" - rimraf "^2.2.8" - through2 "^2.0.0" - vinyl "^2.0.1" - -mem-fs@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/mem-fs/-/mem-fs-1.1.3.tgz#b8ae8d2e3fcb6f5d3f9165c12d4551a065d989cc" - integrity sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw= - dependencies: - through2 "^2.0.0" - vinyl "^1.1.0" - vinyl-file "^2.0.0" - -mem@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" - integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= - dependencies: - mimic-fn "^1.0.0" - -mem@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf" - integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^1.0.0" - p-is-promise "^1.1.0" - "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -20418,7 +19701,7 @@ memory-fs@^0.2.0: resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" integrity sha1-8rslNovBIeORwlIN6Slpyu4KApA= -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -20434,7 +19717,7 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.0.0, meow@^3.3.0, meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -20577,7 +19860,7 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -20654,7 +19937,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@3.0.x, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -21044,16 +20327,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multimatch@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" - multimatch@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" @@ -21113,11 +20386,6 @@ mute-stream@0.0.5: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= -mute-stream@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" - integrity sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s= - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -21324,11 +20592,6 @@ node-fetch@2.1.2, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - node-forge@^0.10.0, node-forge@^0.7.6: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -21592,15 +20855,6 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - normalize-url@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -21618,34 +20872,11 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" -npm-conf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - -npm-keyword@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/npm-keyword/-/npm-keyword-5.0.0.tgz#99b85aec29fcb388d2dd351f0013bf5268787e67" - integrity sha1-mbha7Cn8s4jS3TUfABO/Umh4fmc= - dependencies: - got "^7.1.0" - registry-url "^3.0.3" - npm-normalize-package-bin@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== -npm-run-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" - integrity sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8= - dependencies: - path-key "^1.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -21686,15 +20917,6 @@ npmconf@^2.1.3: gauge "~2.7.3" set-blocking "~2.0.0" -npmlog@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692" - integrity sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI= - dependencies: - ansi "~0.3.1" - are-we-there-yet "~1.1.2" - gauge "~1.2.5" - nth-check@^1.0.2, nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -21772,11 +20994,6 @@ object-assign@4.X, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-assign@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= - object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" @@ -21843,11 +21060,6 @@ object-path-immutable@^3.1.1: dependencies: is-plain-object "3.0.0" -object-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/object-values/-/object-values-1.0.0.tgz#72af839630119e5b98c3b02bb8c27e3237158105" - integrity sha1-cq+DljARnluYw7AruMJ+MjcVgQU= - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -22047,7 +21259,7 @@ opentracing@^0.14.3: resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.4.tgz#a113408ea740da3a90fde5b3b0011a375c2e4268" integrity sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA== -opn@^5.3.0, opn@^5.5.0: +opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== @@ -22163,23 +21375,6 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-locale@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - -os-name@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-2.0.1.tgz#b9a386361c17ae3a21736ef0599405c9a8c5dc5e" - integrity sha1-uaOGNhwXrjohc27wWZQFyajF3F4= - dependencies: - macos-release "^1.0.0" - win-release "^1.0.0" - os-name@^3.0.0, os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -22230,23 +21425,6 @@ p-all@^2.1.0: dependencies: p-map "^2.0.0" -p-any@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-any/-/p-any-1.1.0.tgz#1d03835c7eed1e34b8e539c47b7b60d0d015d4e1" - integrity sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g== - dependencies: - p-some "^2.0.0" - -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== - -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -22257,11 +21435,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -22286,11 +21459,6 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -22374,20 +21542,6 @@ p-retry@^4.2.0: "@types/retry" "^0.12.0" retry "^0.12.0" -p-some@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-some/-/p-some-2.0.1.tgz#65d87c8b154edbcf5221d167778b6d2e150f6f06" - integrity sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY= - dependencies: - aggregate-error "^1.0.0" - -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y= - dependencies: - p-finally "^1.0.0" - p-timeout@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" @@ -22423,26 +21577,6 @@ package-json@^1.0.0: got "^3.2.0" registry-url "^3.0.0" -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= - dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - -package-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-5.0.0.tgz#a7dbe2725edcc7dc9bcee627672275e323882433" - integrity sha512-EeHQFFTlEmLrkIQoxbE9w0FuAWHoc1XpthDqnZ/i9keOt701cteyXwAxQFLpVqVjj3feh2TodkihjLaRUtIgLg== - dependencies: - got "^8.3.1" - registry-auth-token "^3.3.2" - registry-url "^3.1.0" - semver "^5.5.0" - package-json@^6.3.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" @@ -22453,11 +21587,6 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pad-component@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/pad-component/-/pad-component-0.0.1.tgz#ad1f22ce1bf0fdc0d6ddd908af17f351a404b8ac" - integrity sha1-rR8izhvw/cDW3dkIrxfzUaQEuKw= - pad-right@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" @@ -22600,13 +21729,6 @@ parse-headers@^2.0.0: for-each "^0.3.2" trim "0.0.1" -parse-help@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-help/-/parse-help-1.0.0.tgz#a260363ec71a96c0bad4a2ce0208c14a35dd0349" - integrity sha512-dlOrbBba6Rrw/nrJ+V7/vkGZdiimWJQzMHZZrYsUq03JE8AV3fAv6kOYX7dP/w2h67lIdmRf8ES8mU44xAgE/Q== - dependencies: - execall "^1.0.0" - parse-json@^2.1.0, parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -22715,14 +21837,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -passwd-user@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/passwd-user/-/passwd-user-2.1.0.tgz#fad9db6ae252f8b088e0c5decd20a7da0c5d9f1e" - integrity sha1-+tnbauJS+LCI4MXezSCn2gxdnx4= - dependencies: - execa "^0.4.0" - pify "^2.3.0" - password-prompt@^1.0.7: version "1.1.2" resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" @@ -22775,11 +21889,6 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-key@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" - integrity sha1-XVPVeAGWRsDWiADbThRua9wqx68= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -22956,7 +22065,7 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: +pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -23295,11 +22404,6 @@ prettier@~2.0.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== -pretty-bytes@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" - integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= - pretty-bytes@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" @@ -23675,15 +22779,6 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^6.13.2: version "6.13.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.2.tgz#3585aa9412c957cbd358fd5eaca7466f05586dda" @@ -23845,7 +22940,7 @@ rbush@^3.0.1: dependencies: quickselect "^2.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: +rc@^1.0.1, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -24582,14 +23677,6 @@ read-all-stream@^3.0.0: pinkie-promise "^2.0.0" readable-stream "^2.0.0" -read-chunk@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-2.1.0.tgz#6a04c0928005ed9d42e1a6ac5600e19cbc7ff655" - integrity sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU= - dependencies: - pify "^3.0.0" - safe-buffer "^5.1.1" - read-installed@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" @@ -24639,14 +23726,6 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" @@ -24674,15 +23753,6 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" @@ -25035,14 +24105,6 @@ regexpu-core@^4.7.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" -registry-auth-token@^3.0.1, registry-auth-token@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" - integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - registry-auth-token@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" @@ -25050,7 +24112,7 @@ registry-auth-token@^4.0.0: dependencies: rc "^1.2.8" -registry-url@^3.0.0, registry-url@^3.0.3, registry-url@^3.1.0: +registry-url@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= @@ -25302,11 +24364,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= - replace-ext@1.0.0, replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -25361,7 +24418,7 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.81.0, request@2.88.0, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.81.0, request@2.88.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -25579,7 +24636,7 @@ resolve@~1.10.1: dependencies: path-parse "^1.0.6" -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -25658,7 +24715,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.3, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -25722,14 +24779,6 @@ rollup@^0.25.8: minimist "^1.2.0" source-map-support "^0.3.2" -root-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/root-check/-/root-check-1.0.0.tgz#c52a794bf0db9fad567536e41898f0c9e0a86697" - integrity sha1-xSp5S/Dbn61WdTbkGJjwyeCoZpc= - dependencies: - downgrade-root "^1.0.0" - sudo-block "^1.1.0" - rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -25757,7 +24806,7 @@ run-async@^0.1.0: dependencies: once "^1.3.0" -run-async@^2.0.0, run-async@^2.2.0, run-async@^2.4.0: +run-async@^2.2.0, run-async@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== @@ -25791,11 +24840,6 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" - integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= - rxjs-marbles@^5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/rxjs-marbles/-/rxjs-marbles-5.0.6.tgz#e8e71df3b82b49603555f017f2fd3d8c359c4c24" @@ -25803,13 +24847,6 @@ rxjs-marbles@^5.0.6: dependencies: fast-equals "^2.0.0" -rxjs@^5.5.2: - version "5.5.12" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" - integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== - dependencies: - symbol-observable "1.0.1" - rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.1, rxjs@^6.5.3, rxjs@^6.5.5, rxjs@^6.6.0: version "6.6.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" @@ -25996,11 +25033,6 @@ scope-analyzer@^2.0.1: estree-is-function "^1.0.0" get-assigned-identifiers "^1.1.0" -scoped-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8" - integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg= - screenfull@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6" @@ -26049,11 +25081,11 @@ selenium-webdriver@^4.0.0-alpha.7: tmp "0.0.30" selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== dependencies: - node-forge "0.9.0" + node-forge "^0.10.0" semver-diff@^2.0.0: version "2.1.0" @@ -26076,24 +25108,12 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -semver-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" - integrity sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk= - -semver-truncate@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-1.1.2.tgz#57f41de69707a62709a7e0104ba2117109ea47e8" - integrity sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g= - dependencies: - semver "^5.3.0" - "semver@2 || 3 || 4": version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -26225,7 +25245,7 @@ set-harmonic-interval@^1.0.1: resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== -set-immediate-shim@^1.0.0, set-immediate-shim@~1.0.1: +set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= @@ -26359,15 +25379,6 @@ shelljs@^0.6.0: resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" integrity sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg= -shelljs@^0.7.0: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM= - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shelljs@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" @@ -26574,14 +25585,6 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -sort-on@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/sort-on/-/sort-on-3.0.0.tgz#8094005281bf450e91ac4cb4c4cf00c3d6569c41" - integrity sha512-e2RHeY1iM6dT9od3RoqeJSyz3O7naNFsGy34+EFEcwghjAncuOXC2/Xwq87S4FbypqLVp6PcizYEsGEGsGIDXA== - dependencies: - arrify "^1.0.0" - dot-prop "^4.1.1" - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -27130,14 +26133,6 @@ string-length@^1.0.0: dependencies: strip-ansi "^3.0.0" -string-length@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" - integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= - dependencies: - astral-regex "^1.0.0" - strip-ansi "^4.0.0" - string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -27330,14 +26325,6 @@ strip-ansi@~0.1.0: resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE= -strip-bom-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca" - integrity sha1-+H217yYT9paKpUWr/h7HKLaoKco= - dependencies: - first-chunk-stream "^2.0.0" - strip-bom "^2.0.0" - strip-bom-string@1.X: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" @@ -27471,15 +26458,6 @@ success-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/success-symbol/-/success-symbol-0.1.0.tgz#24022e486f3bf1cdca094283b769c472d3b72897" integrity sha1-JAIuSG878c3KCUKDt2nEctO3KJc= -sudo-block@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/sudo-block/-/sudo-block-1.2.0.tgz#cc539bf8191624d4f507d83eeb45b4cea27f3463" - integrity sha1-zFOb+BkWJNT1B9g+60W0zqJ/NGM= - dependencies: - chalk "^1.0.0" - is-docker "^1.0.0" - is-root "^1.0.0" - superagent@3.8.2: version "3.8.2" resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" @@ -27542,13 +26520,6 @@ supports-color@6.0.0: dependencies: has-flag "^3.0.0" -supports-color@6.1.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -27559,13 +26530,6 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" - supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -27573,6 +26537,13 @@ supports-color@^5.0.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-co dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^7.0.0, supports-color@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" @@ -27650,11 +26621,6 @@ swap-case@^1.1.0: lower-case "^1.1.1" upper-case "^1.1.1" -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= - symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -27711,26 +26677,6 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" -tabtab@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/tabtab/-/tabtab-1.3.2.tgz#bb9c2ca6324f659fde7634c2caf3c096e1187ca7" - integrity sha1-u5wspjJPZZ/edjTCyvPAluEYfKc= - dependencies: - debug "^2.2.0" - inquirer "^1.0.2" - minimist "^1.2.0" - mkdirp "^0.5.1" - npmlog "^2.0.3" - object-assign "^4.1.0" - -taketalk@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/taketalk/-/taketalk-1.0.0.tgz#b4d4f0deed206ae7df775b129ea2ca6de52f26dd" - integrity sha1-tNTw3u0gauffd1sSnqLKbeUvJt0= - dependencies: - get-stdin "^4.0.1" - minimist "^1.1.0" - tapable@^0.1.8: version "0.1.10" resolved "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" @@ -27891,13 +26837,6 @@ tempy@^0.3.0: type-fest "^0.3.1" unique-string "^1.0.0" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" - term-size@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" @@ -27989,11 +26928,6 @@ text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -textextensions@2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.2.0.tgz#38ac676151285b658654581987a0ce1a4490d286" - integrity sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA== - thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -28074,11 +27008,6 @@ timed-out@^2.0.0: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= -timed-out@^4.0.0, timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - timers-browserify@^1.0.1: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -28174,11 +27103,6 @@ title-case@^2.1.0, title-case@^2.1.1: no-case "^2.2.0" upper-case "^1.0.3" -titleize@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/titleize/-/titleize-1.0.1.tgz#21bc24fcca658eadc6d3bd3c38f2bd173769b4c5" - integrity sha512-rUwGDruKq1gX+FFHbTl5qjI7teVO7eOe+C8IcQ7QT+1BK3eEUXJqbZcBOeaRP4FwSC/C1A5jDoIVta0nIQ9yew== - tmp@0.0.30: version "0.0.30" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed" @@ -28186,13 +27110,6 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" -tmp@^0.0.29: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" - integrity sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA= - dependencies: - os-tmpdir "~1.0.1" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -28348,7 +27265,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.0.0, tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: +tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -28431,10 +27348,10 @@ trim-trailing-lines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz#7aefbb7808df9d669f6da2e438cac8c46ada7684" integrity sha1-eu+7eAjfnWafbaLkOMrIxGradoQ= -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= +trim@0.0.1, trim@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.3.tgz#05243a47a3a4113e6b49367880a9cca59697a20b" + integrity sha512-h82ywcYhHK7veeelXrCScdH7HkWfbIT1D/CgYO+nmDarz3SGNssVBMws6jU16Ga60AJCRAvPV6w6RLuNerQqjg== triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" @@ -28566,25 +27483,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twig@^1.10.5: - version "1.12.0" - resolved "https://registry.yarnpkg.com/twig/-/twig-1.12.0.tgz#04450bf18ee05532ff70098f10b07227f956b8cf" - integrity sha512-zm5OQXb8bQDGQUPytFgjqMKHhqcz/s6pU6Nwsy+rKPhsoOOVwYeHnziiDGFzeTDiFd28M8EVkEO8we6ikcrGjQ== - dependencies: - locutus "^2.0.5" - minimatch "3.0.x" - walk "2.3.x" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -29156,18 +28059,6 @@ unstated@^2.1.1: dependencies: create-react-context "^0.1.5" -untildify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" - integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA= - dependencies: - os-homedir "^1.0.0" - -untildify@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" - integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -29178,11 +28069,6 @@ unzip-response@^1.0.0: resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" integrity sha1-uYTwh3/AqJwsdzzB73tbIytbBv4= -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= - upath@^1.1.0, upath@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" @@ -29201,22 +28087,6 @@ update-notifier@^0.5.0: semver-diff "^2.0.0" string-length "^1.0.0" -update-notifier@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - update-notifier@^4.0.0, update-notifier@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" @@ -29310,11 +28180,6 @@ url-template@^2.0.8: resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= - url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -29479,7 +28344,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.0.0, uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: +uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -29489,10 +28354,10 @@ uuid@^8.0.0, uuid@^8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== -v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== v8-to-istanbul@^5.0.1: version "5.0.1" @@ -30004,18 +28869,6 @@ vfile@^4.0.0, vfile@^4.2.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -vinyl-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a" - integrity sha1-p+v1/779obfRjRQPyweyI++2dRo= - dependencies: - graceful-fs "^4.1.2" - pify "^2.3.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - strip-bom-stream "^2.0.0" - vinyl "^1.1.0" - vinyl-fs@^3.0.0, vinyl-fs@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" @@ -30059,16 +28912,7 @@ vinyl-sourcemaps-apply@^0.2.0: dependencies: source-map "^0.5.1" -vinyl@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^2.0.0, vinyl@^2.0.1, vinyl@^2.1.0, vinyl@^2.2.0: +vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== @@ -30164,7 +29008,7 @@ wait-on@^5.0.1: minimist "^1.2.5" rxjs "^6.5.5" -walk@2.3.x, walk@^2.3.14: +walk@^2.3.14: version "2.3.14" resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.14.tgz#60ec8631cfd23276ae1e7363ce11d626452e1ef3" integrity sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg== @@ -30260,22 +29104,22 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-cli@^3.3.10: - version "3.3.10" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13" - integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg== +webpack-cli@^3.3.12: + version "3.3.12" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" + integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" + chalk "^2.4.2" + cross-spawn "^6.0.5" + enhanced-resolve "^4.1.1" + findup-sync "^3.0.0" + global-modules "^2.0.0" + import-local "^2.0.0" + interpret "^1.4.0" + loader-utils "^1.4.0" + supports-color "^6.1.0" + v8-compile-cache "^2.1.1" + yargs "^13.3.2" webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: version "3.7.2" @@ -30288,7 +29132,7 @@ webpack-dev-middleware@^3.7.0, webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.8.2: +webpack-dev-server@^3.11.0: version "3.11.0" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== @@ -30523,7 +29367,7 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1, which@1.3.1, which@^1.2.14, which@^1.2.8, which@^1.2.9, which@^1.3.1, which@~1.3.0: +which@1, which@1.3.1, which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -30544,7 +29388,7 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -widest-line@^2.0.0, widest-line@^2.0.1: +widest-line@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== @@ -30558,13 +29402,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -win-release@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" - integrity sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk= - dependencies: - semver "^5.0.1" - window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" @@ -30715,7 +29552,7 @@ write-file-atomic@^1.1.2: imurmurhash "^0.1.4" slide "^1.1.5" -write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: +write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== @@ -30798,11 +29635,6 @@ xdg-basedir@^2.0.0: dependencies: os-homedir "^1.0.0" -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -30922,7 +29754,7 @@ yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== -yargs-parser@13.1.2, yargs-parser@^13.1.0, yargs-parser@^13.1.2: +yargs-parser@13.1.2, yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -30960,23 +29792,6 @@ yargs-unparser@1.6.0: lodash "^4.17.15" yargs "^13.3.0" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" @@ -31080,155 +29895,6 @@ yazl@^2.5.1: dependencies: buffer-crc32 "~0.2.3" -yeoman-character@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/yeoman-character/-/yeoman-character-1.1.0.tgz#90d4b5beaf92759086177015b2fdfa2e0684d7c7" - integrity sha1-kNS1vq+SdZCGF3AVsv36LgaE18c= - dependencies: - supports-color "^3.1.2" - -yeoman-doctor@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yeoman-doctor/-/yeoman-doctor-3.0.2.tgz#b9ad20422ba84b3ef03c1d4afa73cc90fa48fe51" - integrity sha512-/KbouQdKgnqxG6K3Tc8VBPAQLPbruQ7KkbinwR+ah507oOFobHnGs8kqj8oMfafY6rXInHdh7nC5YzicCR4Z0g== - dependencies: - ansi-styles "^3.2.0" - bin-version-check "^3.0.0" - chalk "^2.3.0" - each-async "^1.1.1" - latest-version "^3.1.0" - log-symbols "^2.1.0" - object-values "^1.0.0" - semver "^5.0.3" - twig "^1.10.5" - user-home "^2.0.0" - -yeoman-environment@^1.1.0: - version "1.6.6" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-1.6.6.tgz#cd85fa67d156060e440d7807d7ef7cf0d2d1d671" - integrity sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE= - dependencies: - chalk "^1.0.0" - debug "^2.0.0" - diff "^2.1.2" - escape-string-regexp "^1.0.2" - globby "^4.0.0" - grouped-queue "^0.3.0" - inquirer "^1.0.2" - lodash "^4.11.1" - log-symbols "^1.0.1" - mem-fs "^1.1.0" - text-table "^0.2.0" - untildify "^2.0.0" - -yeoman-environment@^2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.3.4.tgz#ae156147a1b85de939366e5438b00cb3eb54c3e9" - integrity sha512-KLxE5ft/74Qj7h3AsQZv8G6MEEHYJwmD5F99nfOVaep3rBzCtbrJKkdqWc7bDV141Nr8UZZsIXmzc3IcCm6E2w== - dependencies: - chalk "^2.4.1" - cross-spawn "^6.0.5" - debug "^3.1.0" - diff "^3.5.0" - escape-string-regexp "^1.0.2" - globby "^8.0.1" - grouped-queue "^0.3.3" - inquirer "^6.0.0" - is-scoped "^1.0.0" - lodash "^4.17.10" - log-symbols "^2.2.0" - mem-fs "^1.1.0" - strip-ansi "^4.0.0" - text-table "^0.2.0" - untildify "^3.0.3" - -yeoman-generator@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/yeoman-generator/-/yeoman-generator-1.1.1.tgz#40c2b4f6cdfbe05e1952fdd72933f0d8925dbdf5" - integrity sha1-QMK09s374F4ZUv3XKTPw2JJdvfU= - dependencies: - async "^2.0.0" - chalk "^1.0.0" - class-extend "^0.1.0" - cli-table "^0.3.1" - cross-spawn "^5.0.1" - dargs "^5.1.0" - dateformat "^2.0.0" - debug "^2.1.0" - detect-conflict "^1.0.0" - error "^7.0.2" - find-up "^2.1.0" - github-username "^3.0.0" - glob "^7.0.3" - istextorbinary "^2.1.0" - lodash "^4.11.1" - mem-fs-editor "^3.0.0" - minimist "^1.2.0" - mkdirp "^0.5.0" - path-exists "^3.0.0" - path-is-absolute "^1.0.0" - pretty-bytes "^4.0.2" - read-chunk "^2.0.0" - read-pkg-up "^2.0.0" - rimraf "^2.2.0" - run-async "^2.0.0" - shelljs "^0.7.0" - text-table "^0.2.0" - through2 "^2.0.0" - user-home "^2.0.0" - yeoman-environment "^1.1.0" - -yo@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/yo/-/yo-2.0.6.tgz#7b562f68a0434237c24a1fd3982f235035839516" - integrity sha512-1OleNumZXtE/Lo/ZDPsMXqOX8oXr8tpBXYgUGEDAONYqLX3/n3PV3BWkXI7Iwq6vhuAAYPRLinncUe30izlcSg== - dependencies: - async "^2.6.1" - chalk "^2.4.1" - cli-list "^0.2.0" - configstore "^3.1.2" - cross-spawn "^6.0.5" - figures "^2.0.0" - fullname "^3.2.0" - global-tunnel-ng "^2.5.3" - got "^8.3.2" - humanize-string "^1.0.2" - inquirer "^6.0.0" - insight "0.10.1" - lodash "^4.17.10" - meow "^3.0.0" - npm-keyword "^5.0.0" - opn "^5.3.0" - package-json "^5.0.0" - parse-help "^1.0.0" - read-pkg-up "^4.0.0" - root-check "^1.0.0" - sort-on "^3.0.0" - string-length "^2.0.0" - tabtab "^1.3.2" - titleize "^1.0.1" - update-notifier "^2.5.0" - user-home "^2.0.0" - yeoman-character "^1.0.0" - yeoman-doctor "^3.0.1" - yeoman-environment "^2.3.0" - yosay "^2.0.2" - -yosay@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/yosay/-/yosay-2.0.2.tgz#a7017e764cd88d64a1ae64812201de5b157adf6d" - integrity sha512-avX6nz2esp7IMXGag4gu6OyQBsMh/SEn+ZybGu3yKPlOTE6z9qJrzG/0X5vCq/e0rPFy0CUYCze0G5hL310ibA== - dependencies: - ansi-regex "^2.0.0" - ansi-styles "^3.0.0" - chalk "^1.0.0" - cli-boxes "^1.0.0" - pad-component "0.0.1" - string-width "^2.0.0" - strip-ansi "^3.0.0" - taketalk "^1.0.0" - wrap-ansi "^2.0.0" - z-schema@~3.18.3: version "3.18.4" resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2"