diff --git a/.i18nrc.json b/.i18nrc.json index dc01a10b6a686b..57dffa4147e525 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -3,6 +3,7 @@ "console": "src/plugins/console", "core": "src/core", "discover": "src/plugins/discover", + "bfetch": "src/plugins/bfetch", "dashboard": "src/plugins/dashboard", "data": "src/plugins/data", "embeddableApi": "src/plugins/embeddable", diff --git a/api_docs/apm.json b/api_docs/apm.json index 95cc86814e99a0..7eee0349fa349e 100644 --- a/api_docs/apm.json +++ b/api_docs/apm.json @@ -309,7 +309,7 @@ ], "source": { "path": "x-pack/plugins/apm/server/plugin.ts", - "lineNumber": 270 + "lineNumber": 269 }, "deprecated": false, "children": [ @@ -331,7 +331,7 @@ ], "source": { "path": "x-pack/plugins/apm/server/plugin.ts", - "lineNumber": 270 + "lineNumber": 269 }, "deprecated": false, "isRequired": true @@ -351,7 +351,7 @@ ], "source": { "path": "x-pack/plugins/apm/server/plugin.ts", - "lineNumber": 289 + "lineNumber": 288 }, "deprecated": false, "children": [], @@ -653,7 +653,13 @@ ">; delete: (deleteParams: { id: string; }) => Promise<", "DeleteResponse", ">; } | undefined>; }; start: () => Promise; }; ruleRegistry: { setup: ", - "RuleDataPluginService", + { + "pluginId": "ruleRegistry", + "scope": "server", + "docId": "kibRuleRegistryPluginApi", + "section": "def-server.RuleRegistryPluginSetupContract", + "text": "RuleRegistryPluginSetupContract" + }, "; start: () => Promise; }; security?: { setup: ", { "pluginId": "security", diff --git a/api_docs/charts.json b/api_docs/charts.json index d74e0209d6395e..29c1c163d19a03 100644 --- a/api_docs/charts.json +++ b/api_docs/charts.json @@ -179,7 +179,9 @@ "type": "Array", "tags": [], "label": "xValues", - "description": [], + "description": [ + "sorted and unquie x values" + ], "signature": [ "number[]" ], @@ -417,7 +419,9 @@ "type": "Object", "tags": [], "label": "splitSeriesAccessorFnMap", - "description": [], + "description": [ + "needed when using `splitSeriesAccessors` as `AccessorFn`" + ], "signature": [ "Map; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; }; readonly auditbeat: { readonly base: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; readonly filter: string; readonly filters: string; readonly geohash_grid: string; readonly histogram: string; readonly ip_range: string; readonly range: string; readonly significant_terms: string; readonly terms: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; readonly min_bucket: string; readonly sum_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative_sum: string; readonly derivative: string; readonly geo_bounds: string; readonly geo_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving_avg: string; readonly percentile_ranks: string; readonly serial_diff: string; readonly std_dev: string; readonly sum: string; readonly top_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; readonly gettingStarted: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Record; readonly alerting: Record; readonly maps: Record; readonly monitoring: Record; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; }" + "{ readonly canvas: { readonly guide: string; }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; }; readonly auditbeat: { readonly base: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly enterpriseSearch: { readonly base: string; readonly appSearchBase: string; readonly workplaceSearchBase: string; }; readonly heartbeat: { readonly base: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite_missing_bucket: string; readonly date_histogram: string; readonly date_range: string; readonly date_format_pattern: string; readonly filter: string; readonly filters: string; readonly geohash_grid: string; readonly histogram: string; readonly ip_range: string; readonly range: string; readonly significant_terms: string; readonly terms: string; readonly avg: string; readonly avg_bucket: string; readonly max_bucket: string; readonly min_bucket: string; readonly sum_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative_sum: string; readonly derivative: string; readonly geo_bounds: string; readonly geo_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving_avg: string; readonly percentile_ranks: string; readonly serial_diff: string; readonly std_dev: string; readonly sum: string; readonly top_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; readonly runtimeFields: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; readonly gettingStarted: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record; readonly ml: Record; readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Record; readonly alerting: Record; readonly maps: Record; readonly monitoring: Record; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly watcher: Record; readonly ccs: Record; readonly plugins: Record; readonly snapshotRestore: Record; readonly ingest: Record; }" ], "source": { "path": "src/core/public/doc_links/doc_links_service.ts", - "lineNumber": 411 + "lineNumber": 412 }, "deprecated": false } @@ -8748,7 +8748,9 @@ "type": "string", "tags": [], "label": "endpoint", - "description": [], + "description": [ + "- String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`." + ], "signature": [ "string" ], @@ -8765,7 +8767,9 @@ "type": "Object", "tags": [], "label": "clientParams", - "description": [], + "description": [ + "- A dictionary of parameters that will be passed directly to the Elasticsearch JS client." + ], "signature": [ "Record" ], @@ -8782,7 +8786,9 @@ "type": "Object", "tags": [], "label": "options", - "description": [], + "description": [ + "- Options that affect the way we call the API and process the result." + ], "signature": [ { "pluginId": "core", @@ -10803,7 +10809,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 87 + "lineNumber": 96 }, "deprecated": false, "children": [ @@ -10824,7 +10830,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 100 + "lineNumber": 109 }, "deprecated": false, "children": [ @@ -10840,7 +10846,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 100 + "lineNumber": 109 }, "deprecated": false, "isRequired": true @@ -10857,7 +10863,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 100 + "lineNumber": 109 }, "deprecated": false, "isRequired": true @@ -10876,7 +10882,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 100 + "lineNumber": 109 }, "deprecated": false, "isRequired": false @@ -10901,7 +10907,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 116 + "lineNumber": 125 }, "deprecated": false, "children": [ @@ -10917,7 +10923,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 116 + "lineNumber": 125 }, "deprecated": false, "isRequired": true @@ -10934,7 +10940,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 116 + "lineNumber": 125 }, "deprecated": false, "isRequired": true @@ -10953,7 +10959,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 116 + "lineNumber": 125 }, "deprecated": false, "isRequired": false @@ -10978,7 +10984,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 129 + "lineNumber": 138 }, "deprecated": false, "children": [ @@ -10994,7 +11000,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 129 + "lineNumber": 138 }, "deprecated": false, "isRequired": true @@ -11013,7 +11019,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 129 + "lineNumber": 138 }, "deprecated": false, "isRequired": false @@ -11038,7 +11044,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 145 + "lineNumber": 154 }, "deprecated": false, "children": [ @@ -11054,7 +11060,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 145 + "lineNumber": 154 }, "deprecated": false, "isRequired": true @@ -11073,7 +11079,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 145 + "lineNumber": 154 }, "deprecated": false, "isRequired": false @@ -19544,7 +19550,7 @@ "The os platform" ], "signature": [ - "\"linux\" | \"aix\" | \"android\" | \"darwin\" | \"freebsd\" | \"openbsd\" | \"sunos\" | \"win32\" | \"cygwin\"" + "\"linux\" | \"aix\" | \"android\" | \"darwin\" | \"freebsd\" | \"openbsd\" | \"sunos\" | \"win32\" | \"cygwin\" | \"netbsd\"" ], "source": { "path": "src/core/server/metrics/collectors/types.ts", @@ -22315,7 +22321,7 @@ ], "source": { "path": "node_modules/@kbn/config/target/deprecation/types.d.ts", - "lineNumber": 70 + "lineNumber": 79 }, "deprecated": false, "initialIsOpen": false diff --git a/api_docs/core_application.json b/api_docs/core_application.json index 13b110900ab530..e890b6f92a2ad6 100644 --- a/api_docs/core_application.json +++ b/api_docs/core_application.json @@ -115,7 +115,9 @@ "type": "string", "tags": [], "label": "basePath", - "description": [], + "description": [ + "the URL path scope for the sub history" + ], "signature": [ "string" ], @@ -207,7 +209,9 @@ "type": "CompoundType", "tags": [], "label": "pathOrLocation", - "description": [], + "description": [ + "a string or location descriptor" + ], "signature": [ "string | ", "LocationDescriptorObject", @@ -266,7 +270,9 @@ "type": "CompoundType", "tags": [], "label": "pathOrLocation", - "description": [], + "description": [ + "a string or location descriptor" + ], "signature": [ "string | ", "LocationDescriptorObject", @@ -323,7 +329,9 @@ "type": "number", "tags": [], "label": "n", - "description": [], + "description": [ + "number of positions in the stack to go. Negative numbers indicate number of entries backward, positive\nnumbers for forwards. If passed 0, the current location will be reloaded. If `n` exceeds the number of\nentries available, this is a no-op." + ], "signature": [ "number" ], @@ -449,7 +457,9 @@ "type": "Function", "tags": [], "label": "listener", - "description": [], + "description": [ + "a function that receives location updates." + ], "signature": [ "(location: ", "Location", diff --git a/api_docs/core_saved_objects.json b/api_docs/core_saved_objects.json index f18d9d7cf12772..adf0612a28faf8 100644 --- a/api_docs/core_saved_objects.json +++ b/api_docs/core_saved_objects.json @@ -161,7 +161,9 @@ "type": "Array", "tags": [], "label": "objects", - "description": [], + "description": [ + "- [{ type, id, attributes, references, migrationVersion }]" + ], "signature": [ { "pluginId": "core", @@ -451,7 +453,9 @@ "type": "Array", "tags": [], "label": "objects", - "description": [], + "description": [ + "- an array ids, or an array of objects containing id and optionally type" + ], "signature": [ "{ id: string; type: string; }[]" ], @@ -9362,7 +9366,9 @@ "type": "string", "tags": [], "label": "namespace", - "description": [], + "description": [ + "The namespace ID, which must be either a non-empty string or `undefined`." + ], "signature": [ "string | undefined" ], @@ -9400,7 +9406,9 @@ "type": "string", "tags": [], "label": "namespace", - "description": [], + "description": [ + "The namespace string, which must be non-empty." + ], "signature": [ "string" ], diff --git a/api_docs/data.json b/api_docs/data.json index ab5196934d855b..1ba62ea9946549 100644 --- a/api_docs/data.json +++ b/api_docs/data.json @@ -5610,7 +5610,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -5648,7 +5650,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -5751,7 +5755,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -5789,7 +5795,9 @@ "type": "string", "tags": [], "label": "id", - "description": [], + "description": [ + "optionally clear a single id" + ], "signature": [ "string | undefined" ], @@ -5853,6 +5861,26 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-public.IndexPatternsService.getDefaultId", + "type": "Function", + "tags": [], + "label": "getDefaultId", + "description": [ + "\nGet default index pattern id" + ], + "signature": [ + "() => Promise" + ], + "source": { + "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", + "lineNumber": 206 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-public.IndexPatternsService.setDefault", @@ -5863,30 +5891,30 @@ "\nOptionally set default index pattern, unless force = true" ], "signature": [ - "(id: string, force?: boolean) => Promise" + "(id: string | null, force?: boolean) => Promise" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "children": [ { "parentPluginId": "data", "id": "def-public.IndexPatternsService.setDefault.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "id", "description": [], "signature": [ - "string" + "string | null" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, - "isRequired": true + "isRequired": false }, { "parentPluginId": "data", @@ -5900,7 +5928,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "isRequired": true @@ -5930,7 +5958,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "children": [ @@ -5952,7 +5980,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "isRequired": true @@ -6000,7 +6028,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 235 + "lineNumber": 243 }, "deprecated": false, "children": [ @@ -6030,7 +6058,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 236 + "lineNumber": 244 }, "deprecated": false, "isRequired": true @@ -6054,7 +6082,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 237 + "lineNumber": 245 }, "deprecated": false, "isRequired": false @@ -6086,7 +6114,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "children": [ @@ -6108,7 +6136,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "isRequired": true @@ -6154,7 +6182,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "children": [ @@ -6164,7 +6192,9 @@ "type": "Array", "tags": [], "label": "fields", - "description": [], + "description": [ + ": FieldSpec[]" + ], "signature": [ { "pluginId": "data", @@ -6177,7 +6207,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": true @@ -6188,7 +6218,9 @@ "type": "Object", "tags": [], "label": "fieldAttrs", - "description": [], + "description": [ + ": FieldAttrs" + ], "signature": [ { "pluginId": "data", @@ -6201,7 +6233,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": false @@ -6242,7 +6274,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "children": [ @@ -6267,7 +6299,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "isRequired": true @@ -6299,7 +6331,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "children": [ @@ -6315,7 +6347,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "isRequired": true @@ -6353,7 +6385,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "children": [ @@ -6375,7 +6407,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -6392,7 +6424,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -6432,7 +6464,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "children": [ @@ -6454,7 +6486,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -6473,7 +6505,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -6492,7 +6524,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -6530,7 +6562,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "children": [ @@ -6552,7 +6584,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -6571,7 +6603,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -6601,7 +6633,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 550 + "lineNumber": 558 }, "deprecated": false, "children": [ @@ -6623,7 +6655,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 551 + "lineNumber": 559 }, "deprecated": false, "isRequired": true @@ -6640,7 +6672,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 552 + "lineNumber": 560 }, "deprecated": false, "isRequired": true @@ -6657,7 +6689,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 553 + "lineNumber": 561 }, "deprecated": false, "isRequired": true @@ -6679,7 +6711,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "children": [ @@ -6697,7 +6729,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "isRequired": true @@ -7617,7 +7649,7 @@ "plugin": "maps", "link": { "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 266 + "lineNumber": 273 } }, { @@ -8515,7 +8547,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 103 + "lineNumber": 104 }, "deprecated": false, "children": [ @@ -8531,7 +8563,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 103 + "lineNumber": 104 }, "deprecated": false, "isRequired": true @@ -8560,7 +8592,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 110 + "lineNumber": 111 }, "deprecated": false, "children": [ @@ -8576,7 +8608,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 110 + "lineNumber": 111 }, "deprecated": false, "isRequired": true @@ -10812,6 +10844,22 @@ "lineNumber": 23 }, "deprecated": false + }, + { + "parentPluginId": "data", + "id": "def-public.ApplyGlobalFilterActionContext.controlledBy", + "type": "string", + "tags": [], + "label": "controlledBy", + "description": [], + "signature": [ + "string | undefined" + ], + "source": { + "path": "src/plugins/data/public/actions/apply_filter_action.ts", + "lineNumber": 26 + }, + "deprecated": false } ], "initialIsOpen": false @@ -11489,29 +11537,29 @@ { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 10 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 12 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 360 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 45 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 12 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 10 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 45 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 367 } }, { @@ -14884,21 +14932,21 @@ "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 40 + "lineNumber": 44 } }, { "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 55 + "lineNumber": 60 } }, { "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 60 + "lineNumber": 65 } }, { @@ -15913,7 +15961,7 @@ "plugin": "maps", "link": { "path": "x-pack/plugins/maps/public/embeddable/types.ts", - "lineNumber": 44 + "lineNumber": 45 } }, { @@ -19699,7 +19747,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 41 + "lineNumber": 42 }, "deprecated": false, "initialIsOpen": false @@ -20185,7 +20233,7 @@ "section": "def-common.IndexPattern", "text": "IndexPattern" }, - " | null>; setDefault: (id: string, force?: boolean) => Promise; getFieldsForWildcard: (options: ", + " | null>; getDefaultId: () => Promise; setDefault: (id: string | null, force?: boolean) => Promise; getFieldsForWildcard: (options: ", { "pluginId": "data", "scope": "common", @@ -20311,7 +20359,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 642 + "lineNumber": 650 }, "deprecated": false, "initialIsOpen": false @@ -21055,7 +21103,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false }, @@ -21071,7 +21119,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false } @@ -21914,7 +21962,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 66 + "lineNumber": 67 }, "deprecated": false } @@ -21936,7 +21984,7 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { meta: { negate: boolean; alias: string | null; disabled: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", + ") => { meta: { negate: boolean; alias: string | null; disabled: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", { "pluginId": "data", "scope": "common", @@ -21981,7 +22029,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 77 + "lineNumber": 78 }, "deprecated": false } @@ -22047,7 +22095,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 94 + "lineNumber": 95 }, "deprecated": false } @@ -26095,7 +26143,7 @@ "section": "def-common.IndexPattern", "text": "IndexPattern" }, - " | null>; setDefault: (id: string, force?: boolean) => Promise; getFieldsForWildcard: (options: ", + " | null>; getDefaultId: () => Promise; setDefault: (id: string | null, force?: boolean) => Promise; getFieldsForWildcard: (options: ", { "pluginId": "data", "scope": "common", @@ -28430,7 +28478,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -28468,7 +28518,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -28571,7 +28623,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -28609,7 +28663,9 @@ "type": "string", "tags": [], "label": "id", - "description": [], + "description": [ + "optionally clear a single id" + ], "signature": [ "string | undefined" ], @@ -28673,6 +28729,26 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-server.IndexPatternsService.getDefaultId", + "type": "Function", + "tags": [], + "label": "getDefaultId", + "description": [ + "\nGet default index pattern id" + ], + "signature": [ + "() => Promise" + ], + "source": { + "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", + "lineNumber": 206 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-server.IndexPatternsService.setDefault", @@ -28683,30 +28759,30 @@ "\nOptionally set default index pattern, unless force = true" ], "signature": [ - "(id: string, force?: boolean) => Promise" + "(id: string | null, force?: boolean) => Promise" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "children": [ { "parentPluginId": "data", "id": "def-server.IndexPatternsService.setDefault.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "id", "description": [], "signature": [ - "string" + "string | null" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, - "isRequired": true + "isRequired": false }, { "parentPluginId": "data", @@ -28720,7 +28796,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "isRequired": true @@ -28750,7 +28826,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "children": [ @@ -28772,7 +28848,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "isRequired": true @@ -28820,7 +28896,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 235 + "lineNumber": 243 }, "deprecated": false, "children": [ @@ -28850,7 +28926,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 236 + "lineNumber": 244 }, "deprecated": false, "isRequired": true @@ -28874,7 +28950,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 237 + "lineNumber": 245 }, "deprecated": false, "isRequired": false @@ -28906,7 +28982,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "children": [ @@ -28928,7 +29004,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "isRequired": true @@ -28974,7 +29050,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "children": [ @@ -28984,7 +29060,9 @@ "type": "Array", "tags": [], "label": "fields", - "description": [], + "description": [ + ": FieldSpec[]" + ], "signature": [ { "pluginId": "data", @@ -28997,7 +29075,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": true @@ -29008,7 +29086,9 @@ "type": "Object", "tags": [], "label": "fieldAttrs", - "description": [], + "description": [ + ": FieldAttrs" + ], "signature": [ { "pluginId": "data", @@ -29021,7 +29101,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": false @@ -29062,7 +29142,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "children": [ @@ -29087,7 +29167,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "isRequired": true @@ -29119,7 +29199,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "children": [ @@ -29135,7 +29215,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "isRequired": true @@ -29173,7 +29253,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "children": [ @@ -29195,7 +29275,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -29212,7 +29292,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -29252,7 +29332,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "children": [ @@ -29274,7 +29354,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -29293,7 +29373,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -29312,7 +29392,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -29350,7 +29430,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "children": [ @@ -29372,7 +29452,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -29391,7 +29471,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -29421,7 +29501,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 550 + "lineNumber": 558 }, "deprecated": false, "children": [ @@ -29443,7 +29523,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 551 + "lineNumber": 559 }, "deprecated": false, "isRequired": true @@ -29460,7 +29540,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 552 + "lineNumber": 560 }, "deprecated": false, "isRequired": true @@ -29477,7 +29557,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 553 + "lineNumber": 561 }, "deprecated": false, "isRequired": true @@ -29499,7 +29579,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "children": [ @@ -29517,7 +29597,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "isRequired": true @@ -29619,7 +29699,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -29657,7 +29739,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -29760,7 +29844,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -29798,7 +29884,9 @@ "type": "string", "tags": [], "label": "id", - "description": [], + "description": [ + "optionally clear a single id" + ], "signature": [ "string | undefined" ], @@ -29862,6 +29950,26 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-server.IndexPatternsService.getDefaultId", + "type": "Function", + "tags": [], + "label": "getDefaultId", + "description": [ + "\nGet default index pattern id" + ], + "signature": [ + "() => Promise" + ], + "source": { + "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", + "lineNumber": 206 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-server.IndexPatternsService.setDefault", @@ -29872,30 +29980,30 @@ "\nOptionally set default index pattern, unless force = true" ], "signature": [ - "(id: string, force?: boolean) => Promise" + "(id: string | null, force?: boolean) => Promise" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "children": [ { "parentPluginId": "data", "id": "def-server.IndexPatternsService.setDefault.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "id", "description": [], "signature": [ - "string" + "string | null" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, - "isRequired": true + "isRequired": false }, { "parentPluginId": "data", @@ -29909,7 +30017,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "isRequired": true @@ -29939,7 +30047,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "children": [ @@ -29961,7 +30069,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "isRequired": true @@ -30009,7 +30117,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 235 + "lineNumber": 243 }, "deprecated": false, "children": [ @@ -30039,7 +30147,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 236 + "lineNumber": 244 }, "deprecated": false, "isRequired": true @@ -30063,7 +30171,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 237 + "lineNumber": 245 }, "deprecated": false, "isRequired": false @@ -30095,7 +30203,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "children": [ @@ -30117,7 +30225,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "isRequired": true @@ -30163,7 +30271,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "children": [ @@ -30173,7 +30281,9 @@ "type": "Array", "tags": [], "label": "fields", - "description": [], + "description": [ + ": FieldSpec[]" + ], "signature": [ { "pluginId": "data", @@ -30186,7 +30296,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": true @@ -30197,7 +30307,9 @@ "type": "Object", "tags": [], "label": "fieldAttrs", - "description": [], + "description": [ + ": FieldAttrs" + ], "signature": [ { "pluginId": "data", @@ -30210,7 +30322,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": false @@ -30251,7 +30363,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "children": [ @@ -30276,7 +30388,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "isRequired": true @@ -30308,7 +30420,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "children": [ @@ -30324,7 +30436,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "isRequired": true @@ -30362,7 +30474,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "children": [ @@ -30384,7 +30496,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -30401,7 +30513,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -30441,7 +30553,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "children": [ @@ -30463,7 +30575,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -30482,7 +30594,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -30501,7 +30613,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -30539,7 +30651,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "children": [ @@ -30561,7 +30673,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -30580,7 +30692,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -30610,7 +30722,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 550 + "lineNumber": 558 }, "deprecated": false, "children": [ @@ -30632,7 +30744,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 551 + "lineNumber": 559 }, "deprecated": false, "isRequired": true @@ -30649,7 +30761,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 552 + "lineNumber": 560 }, "deprecated": false, "isRequired": true @@ -30666,7 +30778,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 553 + "lineNumber": 561 }, "deprecated": false, "isRequired": true @@ -30688,7 +30800,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "children": [ @@ -30706,7 +30818,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "isRequired": true @@ -33297,29 +33409,29 @@ { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 10 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 12 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 360 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 45 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 12 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 10 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 45 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 367 } }, { @@ -36850,7 +36962,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 41 + "lineNumber": 42 }, "deprecated": false, "initialIsOpen": false @@ -37651,7 +37763,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false }, @@ -37667,7 +37779,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false } @@ -40886,7 +40998,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false, "children": [ @@ -40902,7 +41014,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false, "isRequired": true @@ -40919,7 +41031,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false, "isRequired": false @@ -42077,7 +42189,9 @@ "type": "Object", "tags": [], "label": "filter", - "description": [], + "description": [ + "The filter to clean" + ], "signature": [ { "pluginId": "data", @@ -42293,7 +42407,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 94 + "lineNumber": 95 }, "deprecated": false, "children": [ @@ -42315,7 +42429,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 94 + "lineNumber": 95 }, "deprecated": false, "isRequired": true @@ -42351,7 +42465,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 91 + "lineNumber": 92 }, "deprecated": false, "children": [ @@ -42373,7 +42487,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 91 + "lineNumber": 92 }, "deprecated": false, "isRequired": true @@ -43571,7 +43685,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 103 + "lineNumber": 104 }, "deprecated": false, "children": [ @@ -43587,7 +43701,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 103 + "lineNumber": 104 }, "deprecated": false, "isRequired": true @@ -43667,7 +43781,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 66 + "lineNumber": 67 }, "deprecated": false, "children": [ @@ -43689,7 +43803,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 66 + "lineNumber": 67 }, "deprecated": false, "isRequired": true @@ -43718,7 +43832,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 110 + "lineNumber": 111 }, "deprecated": false, "children": [ @@ -43734,7 +43848,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 110 + "lineNumber": 111 }, "deprecated": false, "isRequired": true @@ -44254,7 +44368,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 97 + "lineNumber": 98 }, "deprecated": false, "children": [ @@ -44276,7 +44390,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 97 + "lineNumber": 98 }, "deprecated": false, "isRequired": true @@ -44469,7 +44583,7 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { meta: { disabled: boolean; alias: string | null; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", + ") => { meta: { disabled: boolean; alias: string | null; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", { "pluginId": "data", "scope": "common", @@ -44481,7 +44595,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 70 + "lineNumber": 71 }, "deprecated": false, "children": [ @@ -44503,7 +44617,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 70 + "lineNumber": 71 }, "deprecated": false, "isRequired": true @@ -44528,7 +44642,7 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { meta: { negate: boolean; alias: string | null; disabled: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", + ") => { meta: { negate: boolean; alias: string | null; disabled: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: ", { "pluginId": "data", "scope": "common", @@ -44540,7 +44654,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 77 + "lineNumber": 78 }, "deprecated": false, "children": [ @@ -44562,7 +44676,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 77 + "lineNumber": 78 }, "deprecated": false, "isRequired": true @@ -44607,7 +44721,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 84 + "lineNumber": 85 }, "deprecated": false, "children": [ @@ -44629,7 +44743,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 84 + "lineNumber": 85 }, "deprecated": false, "isRequired": true @@ -44665,7 +44779,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 100 + "lineNumber": 101 }, "deprecated": false, "children": [ @@ -44687,7 +44801,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 100 + "lineNumber": 101 }, "deprecated": false, "isRequired": true @@ -45152,7 +45266,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 47 + "lineNumber": 48 }, "deprecated": false, "children": [ @@ -45165,7 +45279,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 48 + "lineNumber": 49 }, "deprecated": false }, @@ -45178,7 +45292,7 @@ "description": [], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 49 + "lineNumber": 50 }, "deprecated": false } @@ -45771,7 +45885,7 @@ "label": "ExistsFilterMeta", "description": [], "signature": [ - "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" + "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" ], "source": { "path": "src/plugins/data/common/es_query/filters/exists_filter.ts", @@ -45887,7 +46001,7 @@ ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", - "lineNumber": 41 + "lineNumber": 42 }, "deprecated": false, "initialIsOpen": false @@ -45900,7 +46014,7 @@ "label": "FilterMeta", "description": [], "signature": [ - "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" + "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" ], "source": { "path": "src/plugins/data/common/es_query/filters/meta_filter.ts", @@ -46178,7 +46292,7 @@ "label": "MissingFilterMeta", "description": [], "signature": [ - "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" + "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" ], "source": { "path": "src/plugins/data/common/es_query/filters/missing_filter.ts", @@ -46339,7 +46453,7 @@ "label": "QueryStringFilterMeta", "description": [], "signature": [ - "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" + "{ alias: string | null; disabled: boolean; negate: boolean; controlledBy?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" ], "source": { "path": "src/plugins/data/common/es_query/filters/query_string_filter.ts", diff --git a/api_docs/data_field_formats.json b/api_docs/data_field_formats.json index 6fe1b64bd555bb..1807c32a18389d 100644 --- a/api_docs/data_field_formats.json +++ b/api_docs/data_field_formats.json @@ -1687,7 +1687,9 @@ "type": "Enum", "tags": [], "label": "fieldType", - "description": [], + "description": [ + "- the field type" + ], "signature": [ { "pluginId": "data", @@ -1710,7 +1712,9 @@ "type": "Array", "tags": [], "label": "esTypes", - "description": [], + "description": [ + "- Array of ES data types" + ], "signature": [ { "pluginId": "data", @@ -1759,7 +1763,9 @@ "type": "string", "tags": [], "label": "formatId", - "description": [], + "description": [ + "- the format id" + ], "signature": [ "string" ], @@ -1878,7 +1884,9 @@ "type": "Array", "tags": [], "label": "esTypes", - "description": [], + "description": [ + "- Array of ES data types" + ], "signature": [ { "pluginId": "data", @@ -1941,7 +1949,9 @@ "type": "Array", "tags": [], "label": "esTypes", - "description": [], + "description": [ + "- Array of ES data types" + ], "signature": [ { "pluginId": "data", diff --git a/api_docs/data_index_patterns.json b/api_docs/data_index_patterns.json index 6d9230cfb6a877..676ded76cb331b 100644 --- a/api_docs/data_index_patterns.json +++ b/api_docs/data_index_patterns.json @@ -3291,7 +3291,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -3329,7 +3331,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -3432,7 +3436,9 @@ "type": "boolean", "tags": [], "label": "refresh", - "description": [], + "description": [ + "Force refresh of index pattern list" + ], "signature": [ "boolean" ], @@ -3470,7 +3476,9 @@ "type": "string", "tags": [], "label": "id", - "description": [], + "description": [ + "optionally clear a single id" + ], "signature": [ "string | undefined" ], @@ -3534,6 +3542,26 @@ "children": [], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-common.IndexPatternsService.getDefaultId", + "type": "Function", + "tags": [], + "label": "getDefaultId", + "description": [ + "\nGet default index pattern id" + ], + "signature": [ + "() => Promise" + ], + "source": { + "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", + "lineNumber": 206 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-common.IndexPatternsService.setDefault", @@ -3544,30 +3572,30 @@ "\nOptionally set default index pattern, unless force = true" ], "signature": [ - "(id: string, force?: boolean) => Promise" + "(id: string | null, force?: boolean) => Promise" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "children": [ { "parentPluginId": "data", "id": "def-common.IndexPatternsService.setDefault.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "id", "description": [], "signature": [ - "string" + "string | null" ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, - "isRequired": true + "isRequired": false }, { "parentPluginId": "data", @@ -3581,7 +3609,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 208 + "lineNumber": 216 }, "deprecated": false, "isRequired": true @@ -3611,7 +3639,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "children": [ @@ -3633,7 +3661,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 219 + "lineNumber": 227 }, "deprecated": false, "isRequired": true @@ -3681,7 +3709,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 235 + "lineNumber": 243 }, "deprecated": false, "children": [ @@ -3711,7 +3739,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 236 + "lineNumber": 244 }, "deprecated": false, "isRequired": true @@ -3735,7 +3763,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 237 + "lineNumber": 245 }, "deprecated": false, "isRequired": false @@ -3767,7 +3795,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "children": [ @@ -3789,7 +3817,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 250 + "lineNumber": 258 }, "deprecated": false, "isRequired": true @@ -3835,7 +3863,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "children": [ @@ -3845,7 +3873,9 @@ "type": "Array", "tags": [], "label": "fields", - "description": [], + "description": [ + ": FieldSpec[]" + ], "signature": [ { "pluginId": "data", @@ -3858,7 +3888,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": true @@ -3869,7 +3899,9 @@ "type": "Object", "tags": [], "label": "fieldAttrs", - "description": [], + "description": [ + ": FieldAttrs" + ], "signature": [ { "pluginId": "data", @@ -3882,7 +3914,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 327 + "lineNumber": 335 }, "deprecated": false, "isRequired": false @@ -3923,7 +3955,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "children": [ @@ -3948,7 +3980,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 343 + "lineNumber": 351 }, "deprecated": false, "isRequired": true @@ -3980,7 +4012,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "children": [ @@ -3996,7 +4028,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 465 + "lineNumber": 473 }, "deprecated": false, "isRequired": true @@ -4034,7 +4066,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "children": [ @@ -4056,7 +4088,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -4073,7 +4105,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 484 + "lineNumber": 492 }, "deprecated": false, "isRequired": true @@ -4113,7 +4145,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "children": [ @@ -4135,7 +4167,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -4154,7 +4186,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -4173,7 +4205,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 509 + "lineNumber": 517 }, "deprecated": false, "isRequired": true @@ -4211,7 +4243,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "children": [ @@ -4233,7 +4265,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -4252,7 +4284,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 522 + "lineNumber": 530 }, "deprecated": false, "isRequired": true @@ -4282,7 +4314,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 550 + "lineNumber": 558 }, "deprecated": false, "children": [ @@ -4304,7 +4336,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 551 + "lineNumber": 559 }, "deprecated": false, "isRequired": true @@ -4321,7 +4353,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 552 + "lineNumber": 560 }, "deprecated": false, "isRequired": true @@ -4338,7 +4370,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 553 + "lineNumber": 561 }, "deprecated": false, "isRequired": true @@ -4360,7 +4392,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "children": [ @@ -4378,7 +4410,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 636 + "lineNumber": 644 }, "deprecated": false, "isRequired": true @@ -5516,29 +5548,29 @@ { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 10 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 12 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 360 + "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", + "lineNumber": 45 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 12 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 10 } }, { "plugin": "maps", "link": { - "path": "x-pack/plugins/maps/public/classes/fields/es_doc_field.ts", - "lineNumber": 45 + "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", + "lineNumber": 367 } }, { @@ -8911,21 +8943,21 @@ "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 40 + "lineNumber": 44 } }, { "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 55 + "lineNumber": 60 } }, { "plugin": "securitySolution", "link": { "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts", - "lineNumber": 60 + "lineNumber": 65 } }, { @@ -9940,7 +9972,7 @@ "plugin": "maps", "link": { "path": "x-pack/plugins/maps/public/embeddable/types.ts", - "lineNumber": 44 + "lineNumber": 45 } }, { @@ -12499,7 +12531,7 @@ "section": "def-common.IndexPattern", "text": "IndexPattern" }, - " | null>; setDefault: (id: string, force?: boolean) => Promise; getFieldsForWildcard: (options: ", + " | null>; getDefaultId: () => Promise; setDefault: (id: string | null, force?: boolean) => Promise; getFieldsForWildcard: (options: ", { "pluginId": "data", "scope": "common", @@ -12625,7 +12657,7 @@ ], "source": { "path": "src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts", - "lineNumber": 642 + "lineNumber": 650 }, "deprecated": false, "initialIsOpen": false diff --git a/api_docs/data_query.json b/api_docs/data_query.json index 3e28da259b5e2f..a9c17111369510 100644 --- a/api_docs/data_query.json +++ b/api_docs/data_query.json @@ -1056,7 +1056,9 @@ "type": "Object", "tags": [], "label": "stateContainer", - "description": [], + "description": [ + "to use for syncing" + ], "signature": [ { "pluginId": "kibanaUtils", @@ -1547,7 +1549,9 @@ "type": "Object", "tags": [], "label": "kbnUrlStateStorage", - "description": [], + "description": [ + "to use for syncing" + ], "signature": [ { "pluginId": "kibanaUtils", @@ -2563,7 +2567,9 @@ "type": "CompoundType", "tags": [], "label": "first", - "description": [], + "description": [ + "The first filter or filter array to compare" + ], "signature": [ { "pluginId": "data", @@ -2595,7 +2601,9 @@ "type": "CompoundType", "tags": [], "label": "second", - "description": [], + "description": [ + "The second filter or filter array to compare" + ], "signature": [ { "pluginId": "data", @@ -2627,7 +2635,9 @@ "type": "Object", "tags": [], "label": "comparatorOptions", - "description": [], + "description": [ + "Parameters to use for comparison" + ], "signature": [ { "pluginId": "data", @@ -2706,7 +2716,9 @@ "type": "Array", "tags": [], "label": "existingFilters", - "description": [], + "description": [ + "- The filters to compare to" + ], "signature": [ { "pluginId": "data", @@ -2730,7 +2742,9 @@ "type": "Array", "tags": [], "label": "filters", - "description": [], + "description": [ + "- The filters being added" + ], "signature": [ { "pluginId": "data", @@ -2754,7 +2768,9 @@ "type": "Object", "tags": [], "label": "comparatorOptions", - "description": [], + "description": [ + "- Parameters to use for comparison" + ], "signature": [ { "pluginId": "data", @@ -3132,7 +3148,9 @@ "type": "Array", "tags": [], "label": "filters", - "description": [], + "description": [ + "The filters to remove duplicates from" + ], "signature": [ { "pluginId": "data", @@ -3156,7 +3174,9 @@ "type": "Any", "tags": [], "label": "comparatorOptions", - "description": [], + "description": [ + "- Parameters to use for comparison" + ], "signature": [ "any" ], diff --git a/api_docs/data_search.json b/api_docs/data_search.json index 082553e94dcf42..d72ae79e79bd87 100644 --- a/api_docs/data_search.json +++ b/api_docs/data_search.json @@ -10230,7 +10230,7 @@ "plugin": "maps", "link": { "path": "x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts", - "lineNumber": 266 + "lineNumber": 273 } }, { @@ -10506,7 +10506,7 @@ "section": "def-common.IndexPatternsService", "text": "IndexPatternsService" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">, dependencies: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"getDefaultId\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">, dependencies: ", { "pluginId": "data", "scope": "common", @@ -10562,7 +10562,7 @@ "section": "def-common.IndexPatternsService", "text": "IndexPatternsService" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"getDefaultId\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">" ], "source": { "path": "src/plugins/data/common/search/search_source/search_source_service.ts", @@ -11620,7 +11620,7 @@ "section": "def-common.IndexPatternsService", "text": "IndexPatternsService" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">, searchSourceDependencies: ", + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"getDefaultId\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">, searchSourceDependencies: ", { "pluginId": "data", "scope": "common", @@ -11658,7 +11658,9 @@ "type": "Object", "tags": [], "label": "indexPatterns", - "description": [], + "description": [ + "The index patterns contract of the data plugin" + ], "signature": [ "Pick<", { @@ -11668,7 +11670,7 @@ "section": "def-common.IndexPatternsService", "text": "IndexPatternsService" }, - ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">" + ", \"get\" | \"delete\" | \"create\" | \"find\" | \"ensureDefaultIndexPattern\" | \"getIds\" | \"getTitles\" | \"getIdsWithTitle\" | \"clearCache\" | \"getCache\" | \"getDefault\" | \"getDefaultId\" | \"setDefault\" | \"getFieldsForWildcard\" | \"getFieldsForIndexPattern\" | \"refreshFields\" | \"fieldArrayToMap\" | \"savedObjectToSpec\" | \"createAndSave\" | \"createSavedObject\" | \"updateSavedObject\">" ], "source": { "path": "src/plugins/data/common/search/search_source/create_search_source.ts", diff --git a/api_docs/deprecations.mdx b/api_docs/deprecations.mdx index 9eee6d51d84ab9..99edad62075bce 100644 --- a/api_docs/deprecations.mdx +++ b/api_docs/deprecations.mdx @@ -946,11 +946,11 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Deprecated API | Reference location | Remove By | | ---------------|-----------|-----------| | | [types.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L8) | - | -| | [types.ts#L44](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L44) | - | -| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | -| | [es_source.ts#L360](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L360) | - | +| | [types.ts#L45](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L45) | - | | | [es_doc_field.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L12) | - | | | [es_doc_field.ts#L45](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L45) | - | +| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | +| | [es_source.ts#L367](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L367) | - | | | [index_pattern_util.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L8) | - | | | [index_pattern_util.ts#L15](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L15) | - | | | [index_pattern_util.ts#L49](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L49) | - | @@ -1041,10 +1041,10 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [create_source_editor.tsx#L35](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx#L35) | - | | | [get_docvalue_source_fields.test.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/get_docvalue_source_fields.test.ts#L10) | - | | | [get_docvalue_source_fields.test.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/get_docvalue_source_fields.test.ts#L12) | - | -| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | -| | [es_source.ts#L360](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L360) | - | | | [es_doc_field.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L12) | - | | | [es_doc_field.ts#L45](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L45) | - | +| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | +| | [es_source.ts#L367](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L367) | - | | | [index_pattern_util.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L8) | - | | | [index_pattern_util.ts#L15](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L15) | - | | | [index_pattern_util.ts#L49](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L49) | - | @@ -1136,11 +1136,11 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [get_docvalue_source_fields.test.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/get_docvalue_source_fields.test.ts#L10) | - | | | [get_docvalue_source_fields.test.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_search_source/get_docvalue_source_fields.test.ts#L12) | - | | | [types.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L8) | - | -| | [types.ts#L44](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L44) | - | -| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | -| | [es_source.ts#L360](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L360) | - | +| | [types.ts#L45](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/embeddable/types.ts#L45) | - | | | [es_doc_field.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L12) | - | | | [es_doc_field.ts#L45](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/fields/es_doc_field.ts#L45) | - | +| | [es_source.ts#L10](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L10) | - | +| | [es_source.ts#L367](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts#L367) | - | | | [index_pattern_util.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L8) | - | | | [index_pattern_util.ts#L15](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L15) | - | | | [index_pattern_util.ts#L49](https://github.com/elastic/kibana/tree/master/x-pack/plugins/maps/public/index_pattern_util.ts#L49) | - | @@ -1612,9 +1612,9 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [types.ts#L41](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/pages/details/types.ts#L41) | - | | | [index.tsx#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx#L12) | - | | | [index.tsx#L34](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx#L34) | - | -| | [middleware.ts#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L40) | - | -| | [middleware.ts#L55](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L55) | - | +| | [middleware.ts#L44](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L44) | - | | | [middleware.ts#L60](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L60) | - | +| | [middleware.ts#L65](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L65) | - | | | [types.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts#L12) | - | | | [types.ts#L28](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts#L28) | - | | | [index.tsx#L15](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx#L15) | - | @@ -1824,9 +1824,9 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [types.ts#L41](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/pages/details/types.ts#L41) | - | | | [index.tsx#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx#L12) | - | | | [index.tsx#L34](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/network/components/network_top_countries_table/index.tsx#L34) | - | -| | [middleware.ts#L40](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L40) | - | -| | [middleware.ts#L55](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L55) | - | +| | [middleware.ts#L44](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L44) | - | | | [middleware.ts#L60](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L60) | - | +| | [middleware.ts#L65](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#L65) | - | | | [types.ts#L12](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts#L12) | - | | | [types.ts#L28](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts#L28) | - | | | [index.tsx#L15](https://github.com/elastic/kibana/tree/master/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx#L15) | - | diff --git a/api_docs/embeddable.json b/api_docs/embeddable.json index c9fee3570688c7..d560148f35b136 100644 --- a/api_docs/embeddable.json +++ b/api_docs/embeddable.json @@ -3847,7 +3847,9 @@ "type": "string", "tags": [], "label": "appId", - "description": [], + "description": [ + "- The id of the app to fetch the title for" + ], "signature": [ "string" ], @@ -4716,7 +4718,9 @@ "type": "CompoundType", "tags": [], "label": "props", - "description": [], + "description": [ + "- {@link EmbeddableRendererProps}" + ], "signature": [ { "pluginId": "embeddable", diff --git a/api_docs/expressions.json b/api_docs/expressions.json index 6d77161ae85a0f..e981414fe7bda9 100644 --- a/api_docs/expressions.json +++ b/api_docs/expressions.json @@ -4875,7 +4875,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to extract references from" + ], "signature": [ { "pluginId": "expressions", @@ -4954,7 +4956,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to extract references from" + ], "signature": [ { "pluginId": "expressions", @@ -5017,7 +5021,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to update" + ], "signature": [ { "pluginId": "expressions", @@ -5040,7 +5046,9 @@ "type": "Array", "tags": [], "label": "references", - "description": [], + "description": [ + "array of saved object references" + ], "signature": [ "SavedObjectReference", "[]" @@ -5096,7 +5104,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to update" + ], "signature": [ { "pluginId": "kibanaUtils", @@ -5119,7 +5129,9 @@ "type": "string", "tags": [], "label": "version", - "description": [], + "description": [ + "defines which migration version to run" + ], "signature": [ "string" ], @@ -25765,7 +25777,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to extract references from" + ], "signature": [ { "pluginId": "expressions", @@ -25844,7 +25858,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to extract references from" + ], "signature": [ { "pluginId": "expressions", @@ -25907,7 +25923,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to update" + ], "signature": [ { "pluginId": "expressions", @@ -25930,7 +25948,9 @@ "type": "Array", "tags": [], "label": "references", - "description": [], + "description": [ + "array of saved object references" + ], "signature": [ "SavedObjectReference", "[]" @@ -25986,7 +26006,9 @@ "type": "Object", "tags": [], "label": "state", - "description": [], + "description": [ + "expression AST to update" + ], "signature": [ { "pluginId": "kibanaUtils", @@ -26009,7 +26031,9 @@ "type": "string", "tags": [], "label": "version", - "description": [], + "description": [ + "defines which migration version to run" + ], "signature": [ "string" ], diff --git a/api_docs/fleet.json b/api_docs/fleet.json index 389a56cccefc5b..043dfcd16c2a79 100644 --- a/api_docs/fleet.json +++ b/api_docs/fleet.json @@ -373,6 +373,30 @@ "lineNumber": 60 }, "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-public.NewPackagePolicy.vars", + "type": "Object", + "tags": [], + "label": "vars", + "description": [], + "signature": [ + "Record | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", + "lineNumber": 61 + }, + "deprecated": false } ], "initialIsOpen": false @@ -1435,7 +1459,7 @@ "label": "integration_details_overview", "description": [], "signature": [ - "({ pkgkey }: ", + "({ pkgkey, integration }: ", "DynamicPagePathValues", ") => string" ], @@ -1450,7 +1474,7 @@ "id": "def-public.pagePathGetters.integration_details_overview.$1", "type": "Object", "tags": [], - "label": "{ pkgkey }", + "label": "{ pkgkey, integration }", "description": [], "signature": [ "DynamicPagePathValues" @@ -1473,13 +1497,13 @@ "label": "integration_details_policies", "description": [], "signature": [ - "({ pkgkey }: ", + "({ pkgkey, integration }: ", "DynamicPagePathValues", ") => string" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 81 + "lineNumber": 82 }, "deprecated": false, "children": [ @@ -1488,14 +1512,14 @@ "id": "def-public.pagePathGetters.integration_details_policies.$1", "type": "Object", "tags": [], - "label": "{ pkgkey }", + "label": "{ pkgkey, integration }", "description": [], "signature": [ "DynamicPagePathValues" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 81 + "lineNumber": 82 }, "deprecated": false, "isRequired": true @@ -1511,13 +1535,13 @@ "label": "integration_details_settings", "description": [], "signature": [ - "({ pkgkey }: ", + "({ pkgkey, integration }: ", "DynamicPagePathValues", ") => string" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 82 + "lineNumber": 84 }, "deprecated": false, "children": [ @@ -1526,14 +1550,14 @@ "id": "def-public.pagePathGetters.integration_details_settings.$1", "type": "Object", "tags": [], - "label": "{ pkgkey }", + "label": "{ pkgkey, integration }", "description": [], "signature": [ "DynamicPagePathValues" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 82 + "lineNumber": 84 }, "deprecated": false, "isRequired": true @@ -1549,13 +1573,13 @@ "label": "integration_details_custom", "description": [], "signature": [ - "({ pkgkey }: ", + "({ pkgkey, integration }: ", "DynamicPagePathValues", ") => string" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 83 + "lineNumber": 86 }, "deprecated": false, "children": [ @@ -1564,14 +1588,14 @@ "id": "def-public.pagePathGetters.integration_details_custom.$1", "type": "Object", "tags": [], - "label": "{ pkgkey }", + "label": "{ pkgkey, integration }", "description": [], "signature": [ "DynamicPagePathValues" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 83 + "lineNumber": 86 }, "deprecated": false, "isRequired": true @@ -1593,7 +1617,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 84 + "lineNumber": 88 }, "deprecated": false, "children": [ @@ -1609,7 +1633,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 84 + "lineNumber": 88 }, "deprecated": false, "isRequired": true @@ -1629,7 +1653,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 86 + "lineNumber": 90 }, "deprecated": false, "children": [], @@ -1647,7 +1671,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 87 + "lineNumber": 91 }, "deprecated": false, "children": [], @@ -1667,7 +1691,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 88 + "lineNumber": 92 }, "deprecated": false, "children": [ @@ -1683,7 +1707,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 88 + "lineNumber": 92 }, "deprecated": false, "isRequired": true @@ -1705,7 +1729,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 89 + "lineNumber": 93 }, "deprecated": false, "children": [ @@ -1721,7 +1745,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 89 + "lineNumber": 93 }, "deprecated": false, "isRequired": true @@ -1737,13 +1761,13 @@ "label": "add_integration_to_policy", "description": [], "signature": [ - "({ pkgkey }: ", + "({ pkgkey, integration }: ", "DynamicPagePathValues", ") => string" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 90 + "lineNumber": 94 }, "deprecated": false, "children": [ @@ -1752,14 +1776,14 @@ "id": "def-public.pagePathGetters.add_integration_to_policy.$1", "type": "Object", "tags": [], - "label": "{ pkgkey }", + "label": "{ pkgkey, integration }", "description": [], "signature": [ "DynamicPagePathValues" ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 90 + "lineNumber": 94 }, "deprecated": false, "isRequired": true @@ -1781,7 +1805,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 91 + "lineNumber": 96 }, "deprecated": false, "children": [ @@ -1797,7 +1821,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 91 + "lineNumber": 96 }, "deprecated": false, "isRequired": true @@ -1817,7 +1841,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 93 + "lineNumber": 98 }, "deprecated": false, "children": [], @@ -1837,7 +1861,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 94 + "lineNumber": 99 }, "deprecated": false, "children": [ @@ -1853,7 +1877,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 94 + "lineNumber": 99 }, "deprecated": false, "isRequired": true @@ -1875,7 +1899,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 95 + "lineNumber": 100 }, "deprecated": false, "children": [ @@ -1891,7 +1915,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 95 + "lineNumber": 100 }, "deprecated": false, "isRequired": true @@ -1911,7 +1935,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 97 + "lineNumber": 102 }, "deprecated": false, "children": [], @@ -1929,7 +1953,7 @@ ], "source": { "path": "x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts", - "lineNumber": 98 + "lineNumber": 103 }, "deprecated": false, "children": [], @@ -4777,7 +4801,7 @@ ], "source": { "path": "x-pack/plugins/fleet/server/services/package_policy.ts", - "lineNumber": 651 + "lineNumber": 655 }, "deprecated": false, "initialIsOpen": false @@ -5376,105 +5400,385 @@ }, { "parentPluginId": "fleet", - "id": "def-common.entries", + "id": "def-common.doesPackageHaveIntegrations", "type": "Function", "tags": [], - "label": "entries", + "label": "doesPackageHaveIntegrations", "description": [], "signature": [ - "(o: T) => [keyof T, T[keyof T]][]" - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/index.ts", - "lineNumber": 35 - }, - "deprecated": false, - "returnComment": [], - "children": [ + "(pkgInfo: ", { - "parentPluginId": "fleet", - "id": "def-common.o", - "type": "Uncategorized", - "tags": [], - "label": "o", - "description": [], - "signature": [ - "T" - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/index.ts", - "lineNumber": 35 - }, - "deprecated": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.fullAgentPolicyToYaml", - "type": "Function", - "tags": [], - "label": "fullAgentPolicyToYaml", - "description": [], - "signature": [ - "(policy: ", + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Installed", + "text": "Installed" + }, + " string" + ", ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.EpmPackageAdditions", + "text": "EpmPackageAdditions" + }, + ">> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NotInstalled", + "text": "NotInstalled" + }, + "> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Installed", + "text": "Installed" + }, + "> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NotInstalled", + "text": "NotInstalled" + }, + "> | (Pick<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryPackage", + "text": "RegistryPackage" + }, + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"installed\"; savedObject: ", + "SavedObject", + "<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Installation", + "text": "Installation" + }, + ">; } & { integration?: string | undefined; id: string; }) | (Pick<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryPackage", + "text": "RegistryPackage" + }, + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"not_installed\"; } & { integration?: string | undefined; id: string; })) => boolean" ], "source": { - "path": "x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts", - "lineNumber": 28 + "path": "x-pack/plugins/fleet/common/services/packages_with_integrations.ts", + "lineNumber": 9 }, "deprecated": false, "children": [ { "parentPluginId": "fleet", - "id": "def-common.fullAgentPolicyToYaml.$1", - "type": "Object", + "id": "def-common.doesPackageHaveIntegrations.$1", + "type": "CompoundType", "tags": [], - "label": "policy", + "label": "pkgInfo", "description": [], "signature": [ { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.FullAgentPolicy", - "text": "FullAgentPolicy" - } - ], - "source": { - "path": "x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts", - "lineNumber": 28 - }, - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.isAgentUpgradeable", - "type": "Function", - "tags": [], - "label": "isAgentUpgradeable", - "description": [], - "signature": [ - "(agent: ", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.Agent", + "section": "def-common.Installed", + "text": "Installed" + }, + "> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NotInstalled", + "text": "NotInstalled" + }, + "> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Installed", + "text": "Installed" + }, + "> | ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.NotInstalled", + "text": "NotInstalled" + }, + "> | (Pick<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryPackage", + "text": "RegistryPackage" + }, + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"installed\"; savedObject: ", + "SavedObject", + "<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Installation", + "text": "Installation" + }, + ">; } & { integration?: string | undefined; id: string; }) | (Pick<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryPackage", + "text": "RegistryPackage" + }, + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"not_installed\"; } & { integration?: string | undefined; id: string; })" + ], + "source": { + "path": "x-pack/plugins/fleet/common/services/packages_with_integrations.ts", + "lineNumber": 9 + }, + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.entries", + "type": "Function", + "tags": [], + "label": "entries", + "description": [], + "signature": [ + "(o: T) => [keyof T, T[keyof T]][]" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/index.ts", + "lineNumber": 35 + }, + "deprecated": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.o", + "type": "Uncategorized", + "tags": [], + "label": "o", + "description": [], + "signature": [ + "T" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/index.ts", + "lineNumber": 35 + }, + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.fullAgentPolicyToYaml", + "type": "Function", + "tags": [], + "label": "fullAgentPolicyToYaml", + "description": [], + "signature": [ + "(policy: ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.FullAgentPolicy", + "text": "FullAgentPolicy" + }, + ") => string" + ], + "source": { + "path": "x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts", + "lineNumber": 28 + }, + "deprecated": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.fullAgentPolicyToYaml.$1", + "type": "Object", + "tags": [], + "label": "policy", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.FullAgentPolicy", + "text": "FullAgentPolicy" + } + ], + "source": { + "path": "x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts", + "lineNumber": 28 + }, + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.isAgentUpgradeable", + "type": "Function", + "tags": [], + "label": "isAgentUpgradeable", + "description": [], + "signature": [ + "(agent: ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Agent", "text": "Agent" }, ", kibanaVersion: string) => boolean" @@ -5827,7 +6131,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/services/package_to_package_policy.ts", - "lineNumber": 46 + "lineNumber": 61 }, "deprecated": false, "children": [ @@ -5849,7 +6153,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/services/package_to_package_policy.ts", - "lineNumber": 47 + "lineNumber": 62 }, "deprecated": false, "isRequired": true @@ -6505,7 +6809,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 221 + "lineNumber": 236 }, "deprecated": false, "children": [ @@ -6518,7 +6822,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 222 + "lineNumber": 237 }, "deprecated": false }, @@ -6534,7 +6838,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 223 + "lineNumber": 238 }, "deprecated": false }, @@ -6550,7 +6854,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 224 + "lineNumber": 239 }, "deprecated": false }, @@ -6581,7 +6885,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 225 + "lineNumber": 240 }, "deprecated": false }, @@ -6594,7 +6898,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 226 + "lineNumber": 241 }, "deprecated": false } @@ -6818,7 +7122,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 214 + "lineNumber": 229 }, "deprecated": false, "children": [ @@ -6831,7 +7135,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 215 + "lineNumber": 230 }, "deprecated": false }, @@ -6844,7 +7148,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 216 + "lineNumber": 231 }, "deprecated": false }, @@ -6857,7 +7161,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 217 + "lineNumber": 232 }, "deprecated": false } @@ -7305,7 +7609,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 33 + "lineNumber": 38 }, "deprecated": false, "children": [ @@ -7321,7 +7625,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 34 + "lineNumber": 39 }, "deprecated": false }, @@ -7337,7 +7641,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 35 + "lineNumber": 40 }, "deprecated": false } @@ -7736,7 +8040,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 320 + "lineNumber": 335 }, "deprecated": false, "children": [ @@ -7749,7 +8053,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 321 + "lineNumber": 336 }, "deprecated": false }, @@ -7762,7 +8066,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 322 + "lineNumber": 337 }, "deprecated": false }, @@ -7810,7 +8114,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 323 + "lineNumber": 338 }, "deprecated": false }, @@ -7826,7 +8130,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 324 + "lineNumber": 339 }, "deprecated": false } @@ -9765,7 +10069,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 18 + "lineNumber": 17 }, "deprecated": false, "children": [ @@ -9777,11 +10081,11 @@ "label": "query", "description": [], "signature": [ - "{ experimental?: boolean | undefined; }" + "{ experimental?: boolean | undefined; include_policy_templates?: boolean | undefined; }" ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", - "lineNumber": 19 + "lineNumber": 18 }, "deprecated": false } @@ -10910,18 +11214,10 @@ "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.Installable", - "text": "Installable" - }, - ">[]" + "[]" ], "source": { "path": "x-pack/plugins/fleet/common/types/rest_spec/epm.ts", @@ -11107,7 +11403,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 391 + "lineNumber": 409 }, "deprecated": false, "children": [ @@ -11120,7 +11416,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 392 + "lineNumber": 410 }, "deprecated": false }, @@ -11136,7 +11432,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 393 + "lineNumber": 411 }, "deprecated": false }, @@ -11152,7 +11448,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 394 + "lineNumber": 412 }, "deprecated": false }, @@ -11168,7 +11464,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 398 + "lineNumber": 416 }, "deprecated": false }, @@ -11184,7 +11480,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 399 + "lineNumber": 417 }, "deprecated": false }, @@ -11200,7 +11496,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 400 + "lineNumber": 418 }, "deprecated": false } @@ -11216,7 +11512,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 384 + "lineNumber": 402 }, "deprecated": false, "children": [ @@ -11232,7 +11528,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 385 + "lineNumber": 403 }, "deprecated": false } @@ -11259,7 +11555,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 339 + "lineNumber": 357 }, "deprecated": false, "children": [ @@ -11282,7 +11578,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 340 + "lineNumber": 358 }, "deprecated": false }, @@ -11305,7 +11601,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 341 + "lineNumber": 359 }, "deprecated": false }, @@ -11328,7 +11624,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 342 + "lineNumber": 360 }, "deprecated": false }, @@ -11344,7 +11640,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 343 + "lineNumber": 361 }, "deprecated": false }, @@ -11357,7 +11653,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 344 + "lineNumber": 362 }, "deprecated": false }, @@ -11370,7 +11666,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 345 + "lineNumber": 363 }, "deprecated": false }, @@ -11386,7 +11682,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 346 + "lineNumber": 364 }, "deprecated": false }, @@ -11399,7 +11695,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 347 + "lineNumber": 365 }, "deprecated": false }, @@ -11412,7 +11708,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 348 + "lineNumber": 366 }, "deprecated": false }, @@ -11428,7 +11724,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 349 + "lineNumber": 367 }, "deprecated": false } @@ -12327,7 +12623,31 @@ "lineNumber": 60 }, "deprecated": false - } + }, + { + "parentPluginId": "fleet", + "id": "def-common.NewPackagePolicy.vars", + "type": "Object", + "tags": [], + "label": "vars", + "description": [], + "signature": [ + "Record | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", + "lineNumber": 61 + }, + "deprecated": false + } ], "initialIsOpen": false }, @@ -12592,11 +12912,11 @@ "section": "def-common.NewPackagePolicy", "text": "NewPackagePolicy" }, - ", \"enabled\" | \"description\" | \"name\" | \"package\" | \"namespace\" | \"policy_id\" | \"output_id\">" + ", \"enabled\" | \"description\" | \"name\" | \"package\" | \"namespace\" | \"policy_id\" | \"output_id\" | \"vars\">" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 67 + "lineNumber": 68 }, "deprecated": false, "children": [ @@ -12609,7 +12929,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 68 + "lineNumber": 69 }, "deprecated": false }, @@ -12632,7 +12952,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 69 + "lineNumber": 70 }, "deprecated": false }, @@ -12648,7 +12968,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 70 + "lineNumber": 71 }, "deprecated": false }, @@ -12661,7 +12981,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 71 + "lineNumber": 72 }, "deprecated": false }, @@ -12674,7 +12994,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 72 + "lineNumber": 73 }, "deprecated": false }, @@ -12687,7 +13007,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 73 + "lineNumber": 74 }, "deprecated": false }, @@ -12700,7 +13020,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 74 + "lineNumber": 75 }, "deprecated": false }, @@ -12713,7 +13033,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 75 + "lineNumber": 76 }, "deprecated": false } @@ -12807,7 +13127,7 @@ "section": "def-common.NewPackagePolicyInput", "text": "NewPackagePolicyInput" }, - ", \"type\" | \"enabled\" | \"config\" | \"keep_enabled\" | \"vars\">" + ", \"type\" | \"enabled\" | \"config\" | \"vars\" | \"keep_enabled\">" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", @@ -12983,7 +13303,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 60 + "lineNumber": 61 }, "deprecated": false, "children": [ @@ -12996,7 +13316,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 61 + "lineNumber": 62 }, "deprecated": false }, @@ -13012,7 +13332,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 62 + "lineNumber": 63 }, "deprecated": false }, @@ -13028,7 +13348,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 63 + "lineNumber": 64 }, "deprecated": false }, @@ -13044,7 +13364,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 64 + "lineNumber": 65 }, "deprecated": false } @@ -13278,6 +13598,29 @@ }, "deprecated": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecManifest.vars", + "type": "Array", + "tags": [], + "label": "vars", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryVarsEntry", + "text": "RegistryVarsEntry" + }, + "[] | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "lineNumber": 25 + }, + "deprecated": false + }, { "parentPluginId": "fleet", "id": "def-common.PackageSpecManifest.owner", @@ -13290,7 +13633,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 25 + "lineNumber": 26 }, "deprecated": false } @@ -13306,7 +13649,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 67 + "lineNumber": 68 }, "deprecated": false, "children": [ @@ -13319,7 +13662,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 68 + "lineNumber": 69 }, "deprecated": false }, @@ -13332,7 +13675,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 69 + "lineNumber": 70 }, "deprecated": false }, @@ -13348,7 +13691,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 70 + "lineNumber": 71 }, "deprecated": false }, @@ -13364,7 +13707,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 71 + "lineNumber": 72 }, "deprecated": false } @@ -13380,7 +13723,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 352 + "lineNumber": 370 }, "deprecated": false, "children": [ @@ -13393,7 +13736,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 353 + "lineNumber": 371 }, "deprecated": false } @@ -13987,7 +14330,7 @@ "section": "def-common.NewPackagePolicy", "text": "NewPackagePolicy" }, - ", \"enabled\" | \"description\" | \"name\" | \"namespace\" | \"policy_id\" | \"output_id\">> & { name: string; package: Partial<", + ", \"enabled\" | \"description\" | \"name\" | \"namespace\" | \"policy_id\" | \"output_id\" | \"vars\">> & { name: string; package: Partial<", { "pluginId": "fleet", "scope": "common", @@ -14243,7 +14586,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 266 + "lineNumber": 281 }, "deprecated": false, "children": [ @@ -14256,7 +14599,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 267 + "lineNumber": 282 }, "deprecated": false }, @@ -14272,7 +14615,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 268 + "lineNumber": 283 }, "deprecated": false }, @@ -14288,7 +14631,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 269 + "lineNumber": 284 }, "deprecated": false }, @@ -14301,7 +14644,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 270 + "lineNumber": 285 }, "deprecated": false }, @@ -14314,7 +14657,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 271 + "lineNumber": 286 }, "deprecated": false }, @@ -14327,7 +14670,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 272 + "lineNumber": 287 }, "deprecated": false }, @@ -14350,7 +14693,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 273 + "lineNumber": 288 }, "deprecated": false }, @@ -14363,7 +14706,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 274 + "lineNumber": 289 }, "deprecated": false }, @@ -14376,7 +14719,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 275 + "lineNumber": 290 }, "deprecated": false }, @@ -14387,9 +14730,12 @@ "tags": [], "label": "[RegistryDataStreamKeys.ingest_pipeline]", "description": [], + "signature": [ + "string | undefined" + ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 276 + "lineNumber": 291 }, "deprecated": false }, @@ -14412,7 +14758,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 277 + "lineNumber": 292 }, "deprecated": false }, @@ -14428,7 +14774,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 278 + "lineNumber": 293 }, "deprecated": false } @@ -14444,7 +14790,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 281 + "lineNumber": 296 }, "deprecated": false, "children": [ @@ -14460,7 +14806,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 282 + "lineNumber": 297 }, "deprecated": false }, @@ -14476,7 +14822,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 283 + "lineNumber": 298 }, "deprecated": false } @@ -14490,25 +14836,29 @@ "tags": [], "label": "RegistryImage", "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryImage", + "text": "RegistryImage" + }, + " extends ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackageSpecIcon", + "text": "PackageSpecIcon" + } + ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 121 + "lineNumber": 126 }, "deprecated": false, "children": [ - { - "parentPluginId": "fleet", - "id": "def-common.RegistryImage.src", - "type": "string", - "tags": [], - "label": "src", - "description": [], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 122 - }, - "deprecated": false - }, { "parentPluginId": "fleet", "id": "def-common.RegistryImage.path", @@ -14518,55 +14868,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 123 - }, - "deprecated": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryImage.title", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "signature": [ - "string | undefined" - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 124 - }, - "deprecated": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryImage.size", - "type": "string", - "tags": [], - "label": "size", - "description": [], - "signature": [ - "string | undefined" - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 125 - }, - "deprecated": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryImage.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string | undefined" - ], - "source": { - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 126 + "lineNumber": 127 }, "deprecated": false } @@ -14582,7 +14884,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 154 + "lineNumber": 168 }, "deprecated": false, "children": [ @@ -14595,7 +14897,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 155 + "lineNumber": 169 }, "deprecated": false }, @@ -14608,7 +14910,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 156 + "lineNumber": 170 }, "deprecated": false }, @@ -14621,7 +14923,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 157 + "lineNumber": 171 }, "deprecated": false }, @@ -14637,7 +14939,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 158 + "lineNumber": 172 }, "deprecated": false }, @@ -14653,7 +14955,23 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 159 + "lineNumber": 173 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryInput.RegistryInputKeys.input_group", + "type": "CompoundType", + "tags": [], + "label": "[RegistryInputKeys.input_group]", + "description": [], + "signature": [ + "\"metrics\" | \"logs\" | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 174 }, "deprecated": false }, @@ -14676,7 +14994,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 160 + "lineNumber": 175 }, "deprecated": false } @@ -14692,7 +15010,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 137 + "lineNumber": 143 }, "deprecated": false, "children": [ @@ -14705,7 +15023,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 138 + "lineNumber": 144 }, "deprecated": false }, @@ -14718,7 +15036,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 139 + "lineNumber": 145 }, "deprecated": false }, @@ -14731,7 +15049,85 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 140 + "lineNumber": 146 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.icons", + "type": "Array", + "tags": [], + "label": "[RegistryPolicyTemplateKeys.icons]", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryImage", + "text": "RegistryImage" + }, + "[] | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 147 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.screenshots", + "type": "Array", + "tags": [], + "label": "[RegistryPolicyTemplateKeys.screenshots]", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.RegistryImage", + "text": "RegistryImage" + }, + "[] | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 148 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.categories", + "type": "Array", + "tags": [], + "label": "[RegistryPolicyTemplateKeys.categories]", + "description": [], + "signature": [ + "(\"custom\" | \"security\" | \"monitoring\" | \"cloud\" | \"kubernetes\" | \"aws\" | \"azure\" | \"config_management\" | \"containers\" | \"crm\" | \"datastore\" | \"elastic_stack\" | \"google_cloud\" | \"languages\" | \"message_queue\" | \"network\" | \"notification\" | \"os_system\" | \"productivity\" | \"support\" | \"ticketing\" | \"version_control\" | \"web\" | undefined)[] | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 149 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.data_streams", + "type": "Array", + "tags": [], + "label": "[RegistryPolicyTemplateKeys.data_streams]", + "description": [], + "signature": [ + "string[] | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 150 }, "deprecated": false }, @@ -14754,7 +15150,23 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 141 + "lineNumber": 151 + }, + "deprecated": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.readme", + "type": "string", + "tags": [], + "label": "[RegistryPolicyTemplateKeys.readme]", + "description": [], + "signature": [ + "string | undefined" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 152 }, "deprecated": false }, @@ -14770,7 +15182,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 142 + "lineNumber": 153 }, "deprecated": false } @@ -14786,7 +15198,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 172 + "lineNumber": 187 }, "deprecated": false, "children": [ @@ -14799,7 +15211,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 173 + "lineNumber": 188 }, "deprecated": false }, @@ -14812,7 +15224,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 174 + "lineNumber": 189 }, "deprecated": false }, @@ -14828,7 +15240,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 175 + "lineNumber": 190 }, "deprecated": false }, @@ -14844,7 +15256,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 176 + "lineNumber": 191 }, "deprecated": false }, @@ -14867,7 +15279,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 177 + "lineNumber": 192 }, "deprecated": false }, @@ -14880,7 +15292,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 178 + "lineNumber": 193 }, "deprecated": false } @@ -14896,7 +15308,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 302 + "lineNumber": 317 }, "deprecated": false, "children": [ @@ -14909,7 +15321,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 303 + "lineNumber": 318 }, "deprecated": false }, @@ -14925,7 +15337,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 304 + "lineNumber": 319 }, "deprecated": false }, @@ -14941,7 +15353,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 305 + "lineNumber": 320 }, "deprecated": false }, @@ -14957,7 +15369,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 306 + "lineNumber": 321 }, "deprecated": false }, @@ -14973,7 +15385,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 307 + "lineNumber": 322 }, "deprecated": false }, @@ -14989,7 +15401,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 308 + "lineNumber": 323 }, "deprecated": false }, @@ -15005,7 +15417,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 309 + "lineNumber": 324 }, "deprecated": false }, @@ -15017,11 +15429,11 @@ "label": "[RegistryVarsEntryKeys.default]", "description": [], "signature": [ - "string | string[] | undefined" + "string | boolean | string[] | undefined" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 310 + "lineNumber": 325 }, "deprecated": false }, @@ -15037,7 +15449,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 311 + "lineNumber": 326 }, "deprecated": false } @@ -15053,7 +15465,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 183 + "lineNumber": 198 }, "deprecated": false, "children": [ @@ -15066,7 +15478,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 184 + "lineNumber": 199 }, "deprecated": false } @@ -15162,7 +15574,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 403 + "lineNumber": 421 }, "deprecated": false, "children": [ @@ -15175,7 +15587,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 404 + "lineNumber": 422 }, "deprecated": false }, @@ -15197,7 +15609,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 405 + "lineNumber": 423 }, "deprecated": false } @@ -15316,7 +15728,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 63 + "lineNumber": 64 }, "deprecated": false, "children": [ @@ -15332,7 +15744,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 64 + "lineNumber": 65 }, "deprecated": false } @@ -15350,7 +15762,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 76 + "lineNumber": 81 }, "deprecated": false, "initialIsOpen": false @@ -15364,7 +15776,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 26 + "lineNumber": 31 }, "deprecated": false, "initialIsOpen": false @@ -15378,7 +15790,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 51 + "lineNumber": 56 }, "deprecated": false, "initialIsOpen": false @@ -15392,7 +15804,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 65 + "lineNumber": 70 }, "deprecated": false, "initialIsOpen": false @@ -15406,7 +15818,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 251 + "lineNumber": 266 }, "deprecated": false, "initialIsOpen": false @@ -15420,7 +15832,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 145 + "lineNumber": 156 }, "deprecated": false, "initialIsOpen": false @@ -15434,7 +15846,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 129 + "lineNumber": 130 }, "deprecated": false, "initialIsOpen": false @@ -15448,7 +15860,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 163 + "lineNumber": 178 }, "deprecated": false, "initialIsOpen": false @@ -15462,7 +15874,7 @@ "description": [], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 287 + "lineNumber": 302 }, "deprecated": false, "initialIsOpen": false @@ -15801,7 +16213,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 45 + "lineNumber": 50 }, "deprecated": false, "initialIsOpen": false @@ -16001,7 +16413,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 89 + "lineNumber": 94 }, "deprecated": false, "initialIsOpen": false @@ -16032,7 +16444,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 367 + "lineNumber": 385 }, "deprecated": false, "initialIsOpen": false @@ -16098,7 +16510,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 229 + "lineNumber": 244 }, "deprecated": false, "initialIsOpen": false @@ -16130,7 +16542,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 46 + "lineNumber": 51 }, "deprecated": false, "initialIsOpen": false @@ -16179,7 +16591,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 228 + "lineNumber": 243 }, "deprecated": false, "initialIsOpen": false @@ -16227,7 +16639,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 213 + "lineNumber": 228 }, "deprecated": false, "initialIsOpen": false @@ -16251,7 +16663,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 212 + "lineNumber": 227 }, "deprecated": false, "initialIsOpen": false @@ -16282,7 +16694,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 85 + "lineNumber": 90 }, "deprecated": false, "initialIsOpen": false @@ -16316,7 +16728,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 382 + "lineNumber": 400 }, "deprecated": false, "initialIsOpen": false @@ -16350,7 +16762,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 43 + "lineNumber": 48 }, "deprecated": false, "initialIsOpen": false @@ -16382,7 +16794,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 240 + "lineNumber": 255 }, "deprecated": false, "initialIsOpen": false @@ -16447,7 +16859,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 246 + "lineNumber": 261 }, "deprecated": false, "initialIsOpen": false @@ -16529,7 +16941,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 41 + "lineNumber": 46 }, "deprecated": false, "initialIsOpen": false @@ -16556,7 +16968,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 372 + "lineNumber": 390 }, "deprecated": false, "initialIsOpen": false @@ -16778,7 +17190,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 356 + "lineNumber": 374 }, "deprecated": false, "initialIsOpen": false @@ -16809,7 +17221,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 87 + "lineNumber": 92 }, "deprecated": false, "initialIsOpen": false @@ -16826,7 +17238,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 24 + "lineNumber": 29 }, "deprecated": false, "initialIsOpen": false @@ -16853,7 +17265,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 358 + "lineNumber": 376 }, "deprecated": false, "initialIsOpen": false @@ -16870,7 +17282,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 39 + "lineNumber": 44 }, "deprecated": false, "initialIsOpen": false @@ -16887,7 +17299,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 38 + "lineNumber": 43 }, "deprecated": false, "initialIsOpen": false @@ -16919,7 +17331,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 235 + "lineNumber": 250 }, "deprecated": false, "initialIsOpen": false @@ -16946,7 +17358,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 369 + "lineNumber": 387 }, "deprecated": false, "initialIsOpen": false @@ -17027,7 +17439,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 245 + "lineNumber": 260 }, "deprecated": false, "initialIsOpen": false @@ -17078,7 +17490,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 363 + "lineNumber": 381 }, "deprecated": false, "initialIsOpen": false @@ -17209,7 +17621,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 376 + "lineNumber": 394 }, "deprecated": false, "initialIsOpen": false @@ -17321,7 +17733,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 335 + "lineNumber": 353 }, "deprecated": false, "initialIsOpen": false @@ -17338,22 +17750,14 @@ "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.Installable", - "text": "Installable" - }, - ">[]" + "[]" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 331 + "lineNumber": 346 }, "deprecated": false, "initialIsOpen": false @@ -17366,14 +17770,7 @@ "label": "PackageListItem", "description": [], "signature": [ - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.Installed", - "text": "Installed" - }, - "> | ", + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"installed\"; savedObject: ", + "SavedObject", + "<", { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.NotInstalled", - "text": "NotInstalled" + "section": "def-common.Installation", + "text": "Installation" }, - "; } & { integration?: string | undefined; id: string; }) | (Pick<", { "pluginId": "fleet", "scope": "common", @@ -17397,11 +17796,11 @@ "section": "def-common.RegistryPackage", "text": "RegistryPackage" }, - ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\">>" + ", \"type\" | \"description\" | \"title\" | \"name\" | \"version\" | \"download\" | \"path\" | \"internal\" | \"data_streams\" | \"release\" | \"icons\" | \"policy_templates\"> & { status: \"not_installed\"; } & { integration?: string | undefined; id: string; })" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 333 + "lineNumber": 347 }, "deprecated": false, "initialIsOpen": false @@ -17455,11 +17854,19 @@ "section": "def-common.PackagePolicyInput", "text": "PackagePolicyInput" }, - "[]; policy_id: string; output_id: string; revision: number; }" + "[]; policy_id: string; output_id: string; vars?: Record | undefined; revision: number; }" ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", - "lineNumber": 78 + "lineNumber": 79 }, "deprecated": false, "initialIsOpen": false @@ -17509,7 +17916,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 334 + "lineNumber": 352 }, "deprecated": false, "initialIsOpen": false @@ -17526,7 +17933,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 28 + "lineNumber": 29 }, "deprecated": false, "initialIsOpen": false @@ -17543,7 +17950,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", - "lineNumber": 53 + "lineNumber": 54 }, "deprecated": false, "initialIsOpen": false @@ -17667,6 +18074,23 @@ "deprecated": false, "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryInputGroup", + "type": "Type", + "tags": [], + "label": "RegistryInputGroup", + "description": [], + "signature": [ + "\"metrics\" | \"logs\"" + ], + "source": { + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "lineNumber": 166 + }, + "deprecated": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.RegistryPackage", @@ -17694,7 +18118,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 93 + "lineNumber": 98 }, "deprecated": false, "initialIsOpen": false @@ -17711,7 +18135,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 120 + "lineNumber": 125 }, "deprecated": false, "initialIsOpen": false @@ -17760,7 +18184,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 192 + "lineNumber": 207 }, "deprecated": false, "initialIsOpen": false @@ -17785,7 +18209,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 190 + "lineNumber": 205 }, "deprecated": false, "initialIsOpen": false @@ -17802,7 +18226,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 286 + "lineNumber": 301 }, "deprecated": false, "initialIsOpen": false @@ -17836,7 +18260,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 380 + "lineNumber": 398 }, "deprecated": false, "initialIsOpen": false @@ -17853,7 +18277,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 220 + "lineNumber": 235 }, "deprecated": false, "initialIsOpen": false @@ -17870,7 +18294,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 181 + "lineNumber": 196 }, "deprecated": false, "initialIsOpen": false @@ -17887,7 +18311,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 182 + "lineNumber": 197 }, "deprecated": false, "initialIsOpen": false @@ -17918,7 +18342,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 208 + "lineNumber": 223 }, "deprecated": false, "initialIsOpen": false @@ -17935,7 +18359,7 @@ ], "source": { "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "lineNumber": 44 + "lineNumber": 49 }, "deprecated": false, "initialIsOpen": false diff --git a/api_docs/home.json b/api_docs/home.json index 2aa23c1b756dfa..a1039beb8d3a24 100644 --- a/api_docs/home.json +++ b/api_docs/home.json @@ -1070,7 +1070,7 @@ }, ") => void; getSampleDatasets: () => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", + "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", "SavedObject", "[]) => void; addAppLinksToSampleDataset: (id: string, appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]) => void; replacePanelInSampleDatasetDashboard: ({ sampleDataId, dashboardId, oldEmbeddableId, embeddableId, embeddableType, embeddableConfig, }: ", "SampleDatasetDashboardPanel", @@ -1093,7 +1093,7 @@ "signature": [ "() => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>" + "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>" ], "source": { "path": "src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts", @@ -1426,7 +1426,7 @@ }, ") => void; getSampleDatasets: () => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", + "[]; previewImagePath: string; overviewDashboard: string; appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", "SavedObject", "[]) => void; addAppLinksToSampleDataset: (id: string, appLinks: Readonly<{} & { label: string; path: string; icon: string; }>[]) => void; replacePanelInSampleDatasetDashboard: ({ sampleDataId, dashboardId, oldEmbeddableId, embeddableId, embeddableType, embeddableConfig, }: ", "SampleDatasetDashboardPanel", diff --git a/api_docs/kibana_react.json b/api_docs/kibana_react.json index 9e144147e6919d..98537d71d7b019 100644 --- a/api_docs/kibana_react.json +++ b/api_docs/kibana_react.json @@ -1055,7 +1055,7 @@ "label": "KibanaPageTemplate", "description": [], "signature": [ - "({ template, pageHeader, children, isEmptyState, restrictWidth, bottomBar, bottomBarProps, ...rest }: React.PropsWithChildren<", + "({ template, pageHeader, children, isEmptyState, restrictWidth, bottomBar, bottomBarProps, pageSideBar, solutionNav, ...rest }: React.PropsWithChildren<", { "pluginId": "kibanaReact", "scope": "public", @@ -1067,7 +1067,7 @@ ], "source": { "path": "src/plugins/kibana_react/public/page_template/page_template.tsx", - "lineNumber": 22 + "lineNumber": 37 }, "deprecated": false, "children": [ @@ -1076,7 +1076,7 @@ "id": "def-public.KibanaPageTemplate.$1", "type": "CompoundType", "tags": [], - "label": "{\n template,\n pageHeader,\n children,\n isEmptyState,\n restrictWidth = true,\n bottomBar,\n bottomBarProps,\n ...rest\n}", + "label": "{\n template,\n pageHeader,\n children,\n isEmptyState,\n restrictWidth = true,\n bottomBar,\n bottomBarProps,\n pageSideBar,\n solutionNav,\n ...rest\n}", "description": [], "signature": [ "React.PropsWithChildren<", @@ -1091,7 +1091,7 @@ ], "source": { "path": "src/plugins/kibana_react/public/page_template/page_template.tsx", - "lineNumber": 22 + "lineNumber": 37 }, "deprecated": false, "isRequired": true @@ -1581,7 +1581,9 @@ "type": "CompoundType", "tags": [], "label": "ReactComp", - "description": [], + "description": [ + "A React component." + ], "signature": [ "React.ComponentType" ], @@ -1667,7 +1669,9 @@ "type": "CompoundType", "tags": [], "label": "node", - "description": [], + "description": [ + "to get a mount point for" + ], "signature": [ "React.ReactNode" ], @@ -3936,7 +3940,9 @@ "type": "Type", "tags": [], "label": "KibanaPageTemplateProps", - "description": [], + "description": [ + "\nA thin wrapper around EuiPageTemplate with a few Kibana specific additions" + ], "signature": [ "(Pick<", "EuiPageProps", @@ -3960,7 +3966,9 @@ "EuiPageContentProps", " | undefined; pageContentBodyProps?: ", "EuiPageContentBodyProps", - " | undefined; } & { isEmptyState?: boolean | undefined; }) | (Pick<", + " | undefined; } & { isEmptyState?: boolean | undefined; solutionNav?: ", + "KibanaPageTemplateSolutionNavProps", + " | undefined; }) | (Pick<", "EuiPageProps", ", \"children\" | \"onClick\" | \"onChange\" | \"color\" | \"onKeyDown\" | \"title\" | \"id\" | \"defaultChecked\" | \"defaultValue\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"className\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"hidden\" | \"lang\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"security\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onError\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"css\" | \"data-test-subj\" | \"grow\" | \"direction\" | \"restrictWidth\"> & ", "DisambiguateSet", @@ -3990,11 +3998,13 @@ "EuiPageContentProps", " | undefined; pageContentBodyProps?: ", "EuiPageContentBodyProps", - " | undefined; } & { isEmptyState?: boolean | undefined; })" + " | undefined; } & { isEmptyState?: boolean | undefined; solutionNav?: ", + "KibanaPageTemplateSolutionNavProps", + " | undefined; })" ], "source": { "path": "src/plugins/kibana_react/public/page_template/page_template.tsx", - "lineNumber": 12 + "lineNumber": 23 }, "deprecated": false, "initialIsOpen": false diff --git a/api_docs/kibana_utils.json b/api_docs/kibana_utils.json index 173348ea2f2638..66eefa9097bfb1 100644 --- a/api_docs/kibana_utils.json +++ b/api_docs/kibana_utils.json @@ -2551,7 +2551,9 @@ "type": "Object", "tags": [], "label": "storage", - "description": [], + "description": [ + "- Option {@link Storage} to use for storing state. By default window.sessionStorage." + ], "signature": [ "Storage" ], @@ -2616,7 +2618,9 @@ "type": "Function", "tags": [], "label": "accessor", - "description": [], + "description": [ + "Asynchronous start service accessor provided by platform." + ], "signature": [ { "pluginId": "core", @@ -3623,7 +3627,9 @@ "type": "Object", "tags": [], "label": "promise", - "description": [], + "description": [ + "Promise to convert to 3-tuple." + ], "signature": [ "Promise" ], @@ -4687,7 +4693,9 @@ "type": "Uncategorized", "tags": [], "label": "container", - "description": [], + "description": [ + "- {@link StateContainer} which state to track." + ], "signature": [ "Container" ], @@ -4704,7 +4712,9 @@ "type": "Function", "tags": [], "label": "selector", - "description": [], + "description": [ + "- Function used to pick parts of state." + ], "signature": [ "(state: ", { @@ -4729,7 +4739,9 @@ "type": "Function", "tags": [], "label": "comparator", - "description": [], + "description": [ + "- {@link Comparator} function used to memoize previous result, to not\nre-render React component if state did not change. By default uses\n`fast-deep-equal` package." + ], "signature": [ { "pluginId": "kibanaUtils", @@ -4793,7 +4805,9 @@ "type": "Uncategorized", "tags": [], "label": "container", - "description": [], + "description": [ + "- {@link StateContainer} which state to track." + ], "signature": [ "Container" ], @@ -10006,7 +10020,9 @@ "type": "Object", "tags": [], "label": "promise", - "description": [], + "description": [ + "Promise to convert to 3-tuple." + ], "signature": [ "Promise" ], @@ -10069,7 +10085,9 @@ "type": "Uncategorized", "tags": [], "label": "container", - "description": [], + "description": [ + "- {@link StateContainer} which state to track." + ], "signature": [ "Container" ], @@ -10086,7 +10104,9 @@ "type": "Function", "tags": [], "label": "selector", - "description": [], + "description": [ + "- Function used to pick parts of state." + ], "signature": [ "(state: ", { @@ -10111,7 +10131,9 @@ "type": "Function", "tags": [], "label": "comparator", - "description": [], + "description": [ + "- {@link Comparator} function used to memoize previous result, to not\nre-render React component if state did not change. By default uses\n`fast-deep-equal` package." + ], "signature": [ { "pluginId": "kibanaUtils", @@ -10175,7 +10197,9 @@ "type": "Uncategorized", "tags": [], "label": "container", - "description": [], + "description": [ + "- {@link StateContainer} which state to track." + ], "signature": [ "Container" ], diff --git a/api_docs/lens.json b/api_docs/lens.json index f81dbe4217f287..196361f65682b9 100644 --- a/api_docs/lens.json +++ b/api_docs/lens.json @@ -1353,7 +1353,7 @@ "description": [], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 473 + "lineNumber": 474 }, "deprecated": false, "children": [ @@ -1369,7 +1369,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 474 + "lineNumber": 475 }, "deprecated": false }, @@ -1391,7 +1391,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 475 + "lineNumber": 476 }, "deprecated": false }, @@ -1407,7 +1407,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 476 + "lineNumber": 477 }, "deprecated": false }, @@ -1423,7 +1423,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 477 + "lineNumber": 478 }, "deprecated": false }, @@ -1440,7 +1440,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 478 + "lineNumber": 479 }, "deprecated": false }, @@ -1457,7 +1457,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 479 + "lineNumber": 480 }, "deprecated": false }, @@ -1480,7 +1480,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 480 + "lineNumber": 481 }, "deprecated": false }, @@ -1496,7 +1496,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 481 + "lineNumber": 482 }, "deprecated": false }, @@ -1512,7 +1512,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 482 + "lineNumber": 483 }, "deprecated": false }, @@ -1528,7 +1528,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 483 + "lineNumber": 484 }, "deprecated": false }, @@ -1551,7 +1551,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 484 + "lineNumber": 485 }, "deprecated": false }, @@ -1574,7 +1574,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 485 + "lineNumber": 486 }, "deprecated": false }, @@ -1597,7 +1597,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 486 + "lineNumber": 487 }, "deprecated": false }, @@ -1613,7 +1613,23 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 487 + "lineNumber": 488 + }, + "deprecated": false + }, + { + "parentPluginId": "lens", + "id": "def-public.XYState.fillOpacity", + "type": "number", + "tags": [], + "label": "fillOpacity", + "description": [], + "signature": [ + "number | undefined" + ], + "source": { + "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", + "lineNumber": 489 }, "deprecated": false }, @@ -1629,7 +1645,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 488 + "lineNumber": 490 }, "deprecated": false } @@ -2093,7 +2109,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts", - "lineNumber": 497 + "lineNumber": 498 }, "deprecated": false, "initialIsOpen": false @@ -2280,7 +2296,7 @@ ], "source": { "path": "x-pack/plugins/lens/public/xy_visualization/types.ts", - "lineNumber": 470 + "lineNumber": 471 }, "deprecated": false, "initialIsOpen": false diff --git a/api_docs/licensing.json b/api_docs/licensing.json index 873f40bd301a1c..1451cff869f47a 100644 --- a/api_docs/licensing.json +++ b/api_docs/licensing.json @@ -3088,7 +3088,7 @@ "plugin": "reporting", "link": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 228 + "lineNumber": 226 } }, { diff --git a/api_docs/maps.json b/api_docs/maps.json index a8c582800fc3b9..d418518dcb0d2c 100644 --- a/api_docs/maps.json +++ b/api_docs/maps.json @@ -57,7 +57,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 90 + "lineNumber": 95 }, "deprecated": false, "children": [ @@ -70,7 +70,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 93 + "lineNumber": 98 }, "deprecated": false }, @@ -86,7 +86,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 110 + "lineNumber": 118 }, "deprecated": false, "children": [ @@ -102,7 +102,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 110 + "lineNumber": 118 }, "deprecated": false, "isRequired": true @@ -125,7 +125,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 110 + "lineNumber": 118 }, "deprecated": false, "isRequired": true @@ -165,7 +165,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 110 + "lineNumber": 118 }, "deprecated": false, "isRequired": false @@ -194,7 +194,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 184 + "lineNumber": 200 }, "deprecated": false, "children": [ @@ -216,7 +216,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 185 + "lineNumber": 201 }, "deprecated": false, "isRequired": true @@ -238,7 +238,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 190 + "lineNumber": 206 }, "deprecated": false, "children": [], @@ -258,7 +258,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 198 + "lineNumber": 214 }, "deprecated": false, "children": [], @@ -276,7 +276,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 203 + "lineNumber": 219 }, "deprecated": false, "children": [], @@ -294,7 +294,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 207 + "lineNumber": 223 }, "deprecated": false, "children": [], @@ -314,7 +314,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 211 + "lineNumber": 227 }, "deprecated": false, "children": [ @@ -330,7 +330,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 211 + "lineNumber": 227 }, "deprecated": false, "isRequired": true @@ -352,7 +352,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 215 + "lineNumber": 231 }, "deprecated": false, "children": [ @@ -368,7 +368,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 215 + "lineNumber": 231 }, "deprecated": false, "isRequired": true @@ -395,7 +395,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 219 + "lineNumber": 235 }, "deprecated": false, "children": [], @@ -413,7 +413,51 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 223 + "lineNumber": 239 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.MapEmbeddable._getFilters", + "type": "Function", + "tags": [], + "label": "_getFilters", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "source": { + "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", + "lineNumber": 283 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.MapEmbeddable._getSearchSessionId", + "type": "Function", + "tags": [], + "label": "_getSearchSessionId", + "description": [], + "signature": [ + "() => string | undefined" + ], + "source": { + "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", + "lineNumber": 291 }, "deprecated": false, "children": [], @@ -431,7 +475,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 255 + "lineNumber": 301 }, "deprecated": false, "children": [ @@ -444,7 +488,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 255 + "lineNumber": 301 }, "deprecated": false, "children": [ @@ -457,7 +501,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 255 + "lineNumber": 301 }, "deprecated": false } @@ -486,7 +530,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 277 + "lineNumber": 321 }, "deprecated": false, "children": [ @@ -508,7 +552,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 277 + "lineNumber": 321 }, "deprecated": false, "isRequired": true @@ -528,7 +572,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 287 + "lineNumber": 331 }, "deprecated": false, "children": [ @@ -544,7 +588,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 287 + "lineNumber": 331 }, "deprecated": false, "isRequired": false @@ -566,7 +610,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 305 + "lineNumber": 349 }, "deprecated": false, "children": [ @@ -582,7 +626,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 305 + "lineNumber": 349 }, "deprecated": false, "isRequired": true @@ -604,7 +648,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 331 + "lineNumber": 375 }, "deprecated": false, "children": [ @@ -621,7 +665,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 331 + "lineNumber": 375 }, "deprecated": false, "isRequired": true @@ -649,7 +693,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 348 + "lineNumber": 392 }, "deprecated": false, "children": [ @@ -665,7 +709,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 348 + "lineNumber": 392 }, "deprecated": false, "isRequired": true @@ -682,7 +726,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 348 + "lineNumber": 392 }, "deprecated": false, "isRequired": true @@ -705,7 +749,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 348 + "lineNumber": 392 }, "deprecated": false, "isRequired": false @@ -733,7 +777,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 362 + "lineNumber": 406 }, "deprecated": false, "children": [ @@ -756,7 +800,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 362 + "lineNumber": 406 }, "deprecated": false, "isRequired": true @@ -773,7 +817,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 362 + "lineNumber": 406 }, "deprecated": false, "isRequired": true @@ -801,7 +845,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 374 + "lineNumber": 418 }, "deprecated": false, "children": [], @@ -827,7 +871,43 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 395 + "lineNumber": 439 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.MapEmbeddable.setMapExtentFilter", + "type": "Function", + "tags": [], + "label": "setMapExtentFilter", + "description": [], + "signature": [ + "() => void" + ], + "source": { + "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", + "lineNumber": 450 + }, + "deprecated": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.MapEmbeddable.clearMapExtentFilter", + "type": "Function", + "tags": [], + "label": "clearMapExtentFilter", + "description": [], + "signature": [ + "() => void" + ], + "source": { + "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", + "lineNumber": 487 }, "deprecated": false, "children": [], @@ -845,7 +925,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 406 + "lineNumber": 501 }, "deprecated": false, "children": [], @@ -863,7 +943,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 422 + "lineNumber": 517 }, "deprecated": false, "children": [], @@ -881,7 +961,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "lineNumber": 428 + "lineNumber": 523 }, "deprecated": false, "children": [], @@ -1297,7 +1377,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/types.ts", - "lineNumber": 41 + "lineNumber": 42 }, "deprecated": false, "initialIsOpen": false @@ -1329,7 +1409,7 @@ ], "source": { "path": "x-pack/plugins/maps/public/embeddable/types.ts", - "lineNumber": 43 + "lineNumber": 44 }, "deprecated": false, "initialIsOpen": false @@ -1535,7 +1615,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 66 + "lineNumber": 67 }, "deprecated": false, "children": [ @@ -1551,7 +1631,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 66 + "lineNumber": 67 }, "deprecated": false, "isRequired": true @@ -1572,7 +1652,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 63 + "lineNumber": 64 }, "deprecated": false, "children": [ @@ -1588,7 +1668,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 63 + "lineNumber": 64 }, "deprecated": false, "isRequired": true @@ -1609,7 +1689,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 60 + "lineNumber": 61 }, "deprecated": false, "children": [], @@ -1627,7 +1707,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 22 + "lineNumber": 23 }, "deprecated": false, "children": [ @@ -1643,7 +1723,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 23 + "lineNumber": 24 }, "deprecated": false } @@ -1663,6 +1743,22 @@ }, "deprecated": false, "children": [ + { + "parentPluginId": "maps", + "id": "def-common.CreateDocSourceResp.indexPatternId", + "type": "string", + "tags": [], + "label": "indexPatternId", + "description": [], + "signature": [ + "string | undefined" + ], + "source": { + "path": "x-pack/plugins/maps/common/types.ts", + "lineNumber": 9 + }, + "deprecated": false + }, { "parentPluginId": "maps", "id": "def-common.CreateDocSourceResp.success", @@ -1672,7 +1768,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 9 + "lineNumber": 10 }, "deprecated": false }, @@ -1688,7 +1784,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 10 + "lineNumber": 11 }, "deprecated": false } @@ -1704,7 +1800,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 13 + "lineNumber": 14 }, "deprecated": false, "children": [ @@ -1720,7 +1816,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 14 + "lineNumber": 15 }, "deprecated": false }, @@ -1736,7 +1832,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 17 + "lineNumber": 18 }, "deprecated": false } @@ -1752,7 +1848,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 26 + "lineNumber": 27 }, "deprecated": false, "children": [ @@ -1765,7 +1861,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 27 + "lineNumber": 28 }, "deprecated": false }, @@ -1781,7 +1877,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 28 + "lineNumber": 29 }, "deprecated": false }, @@ -1797,7 +1893,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/types.ts", - "lineNumber": 29 + "lineNumber": 30 }, "deprecated": false } @@ -1815,7 +1911,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 163 + "lineNumber": 164 }, "deprecated": false, "initialIsOpen": false @@ -1829,7 +1925,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 211 + "lineNumber": 212 }, "deprecated": false, "initialIsOpen": false @@ -1843,7 +1939,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 292 + "lineNumber": 293 }, "deprecated": false, "initialIsOpen": false @@ -1857,7 +1953,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 156 + "lineNumber": 157 }, "deprecated": false, "initialIsOpen": false @@ -1871,7 +1967,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 123 + "lineNumber": 124 }, "deprecated": false, "initialIsOpen": false @@ -1885,7 +1981,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 131 + "lineNumber": 132 }, "deprecated": false, "initialIsOpen": false @@ -1899,7 +1995,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 96 + "lineNumber": 97 }, "deprecated": false, "initialIsOpen": false @@ -1913,7 +2009,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 255 + "lineNumber": 256 }, "deprecated": false, "initialIsOpen": false @@ -1927,7 +2023,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 137 + "lineNumber": 138 }, "deprecated": false, "initialIsOpen": false @@ -1941,7 +2037,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 180 + "lineNumber": 181 }, "deprecated": false, "initialIsOpen": false @@ -1955,7 +2051,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 267 + "lineNumber": 268 }, "deprecated": false, "initialIsOpen": false @@ -1969,7 +2065,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 224 + "lineNumber": 225 }, "deprecated": false, "initialIsOpen": false @@ -1983,7 +2079,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 205 + "lineNumber": 206 }, "deprecated": false, "initialIsOpen": false @@ -1997,7 +2093,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 70 + "lineNumber": 71 }, "deprecated": false, "initialIsOpen": false @@ -2011,7 +2107,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 274 + "lineNumber": 275 }, "deprecated": false, "initialIsOpen": false @@ -2025,7 +2121,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 287 + "lineNumber": 288 }, "deprecated": false, "initialIsOpen": false @@ -2039,7 +2135,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 260 + "lineNumber": 261 }, "deprecated": false, "initialIsOpen": false @@ -2053,7 +2149,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 174 + "lineNumber": 175 }, "deprecated": false, "initialIsOpen": false @@ -2067,7 +2163,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 248 + "lineNumber": 249 }, "deprecated": false, "initialIsOpen": false @@ -2081,7 +2177,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 79 + "lineNumber": 80 }, "deprecated": false, "initialIsOpen": false @@ -2095,7 +2191,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 200 + "lineNumber": 201 }, "deprecated": false, "initialIsOpen": false @@ -2109,7 +2205,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 219 + "lineNumber": 220 }, "deprecated": false, "initialIsOpen": false @@ -2123,7 +2219,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 280 + "lineNumber": 281 }, "deprecated": false, "initialIsOpen": false @@ -2137,7 +2233,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 233 + "lineNumber": 234 }, "deprecated": false, "initialIsOpen": false @@ -2156,7 +2252,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 162 + "lineNumber": 163 }, "deprecated": false, "initialIsOpen": false @@ -2170,7 +2266,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 44 + "lineNumber": 45 }, "deprecated": false, "initialIsOpen": false @@ -2252,7 +2348,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 216 + "lineNumber": 217 }, "deprecated": false, "initialIsOpen": false @@ -2266,7 +2362,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 194 + "lineNumber": 195 }, "deprecated": false, "initialIsOpen": false @@ -2283,7 +2379,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 198 + "lineNumber": 199 }, "deprecated": false, "initialIsOpen": false @@ -2300,7 +2396,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 112 + "lineNumber": 113 }, "deprecated": false, "initialIsOpen": false @@ -2317,7 +2413,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 231 + "lineNumber": 232 }, "deprecated": false, "initialIsOpen": false @@ -2334,7 +2430,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 116 + "lineNumber": 117 }, "deprecated": false, "initialIsOpen": false @@ -2351,7 +2447,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 115 + "lineNumber": 116 }, "deprecated": false, "initialIsOpen": false @@ -2368,7 +2464,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 114 + "lineNumber": 115 }, "deprecated": false, "initialIsOpen": false @@ -2385,7 +2481,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 192 + "lineNumber": 193 }, "deprecated": false, "initialIsOpen": false @@ -2402,7 +2498,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 296 + "lineNumber": 297 }, "deprecated": false, "initialIsOpen": false @@ -2657,7 +2753,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 307 + "lineNumber": 308 }, "deprecated": false, "initialIsOpen": false @@ -2674,7 +2770,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 308 + "lineNumber": 309 }, "deprecated": false, "initialIsOpen": false @@ -2691,7 +2787,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 306 + "lineNumber": 307 }, "deprecated": false, "initialIsOpen": false @@ -2708,7 +2804,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 129 + "lineNumber": 130 }, "deprecated": false, "initialIsOpen": false @@ -2725,7 +2821,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 118 + "lineNumber": 119 }, "deprecated": false, "initialIsOpen": false @@ -2742,7 +2838,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 119 + "lineNumber": 120 }, "deprecated": false, "initialIsOpen": false @@ -2767,7 +2863,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 300 + "lineNumber": 301 }, "deprecated": false, "initialIsOpen": false @@ -2798,7 +2894,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 103 + "lineNumber": 104 }, "deprecated": false, "initialIsOpen": false @@ -2815,7 +2911,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 189 + "lineNumber": 190 }, "deprecated": false, "initialIsOpen": false @@ -2832,7 +2928,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 188 + "lineNumber": 189 }, "deprecated": false, "initialIsOpen": false @@ -2851,6 +2947,20 @@ "deprecated": false, "initialIsOpen": false }, + { + "parentPluginId": "maps", + "id": "def-common.INDEX_FEATURE_PATH", + "type": "string", + "tags": [], + "label": "INDEX_FEATURE_PATH", + "description": [], + "source": { + "path": "x-pack/plugins/maps/common/constants.ts", + "lineNumber": 44 + }, + "deprecated": false, + "initialIsOpen": false + }, { "parentPluginId": "maps", "id": "def-common.INDEX_META_DATA_CREATED_BY", @@ -2863,7 +2973,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 302 + "lineNumber": 303 }, "deprecated": false, "initialIsOpen": false @@ -2925,7 +3035,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 100 + "lineNumber": 101 }, "deprecated": false, "initialIsOpen": false @@ -2942,7 +3052,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 55 + "lineNumber": 56 }, "deprecated": false, "initialIsOpen": false @@ -2959,7 +3069,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 52 + "lineNumber": 53 }, "deprecated": false, "initialIsOpen": false @@ -2976,7 +3086,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 51 + "lineNumber": 52 }, "deprecated": false, "initialIsOpen": false @@ -2993,7 +3103,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 149 + "lineNumber": 150 }, "deprecated": false, "initialIsOpen": false @@ -3010,7 +3120,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 148 + "lineNumber": 149 }, "deprecated": false, "initialIsOpen": false @@ -3075,7 +3185,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 304 + "lineNumber": 305 }, "deprecated": false, "initialIsOpen": false @@ -3092,7 +3202,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 110 + "lineNumber": 111 }, "deprecated": false, "initialIsOpen": false @@ -3109,7 +3219,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 121 + "lineNumber": 122 }, "deprecated": false, "initialIsOpen": false @@ -3126,7 +3236,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 102 + "lineNumber": 103 }, "deprecated": false, "initialIsOpen": false @@ -3143,7 +3253,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 109 + "lineNumber": 110 }, "deprecated": false, "initialIsOpen": false @@ -3160,7 +3270,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 47 + "lineNumber": 48 }, "deprecated": false, "initialIsOpen": false @@ -3177,7 +3287,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 46 + "lineNumber": 47 }, "deprecated": false, "initialIsOpen": false @@ -3194,7 +3304,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 48 + "lineNumber": 49 }, "deprecated": false, "initialIsOpen": false @@ -3211,7 +3321,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 57 + "lineNumber": 58 }, "deprecated": false, "initialIsOpen": false @@ -3228,7 +3338,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 217 + "lineNumber": 218 }, "deprecated": false, "initialIsOpen": false @@ -3245,7 +3355,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 147 + "lineNumber": 148 }, "deprecated": false, "initialIsOpen": false @@ -3262,7 +3372,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 298 + "lineNumber": 299 }, "deprecated": false, "initialIsOpen": false @@ -3276,7 +3386,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 107 + "lineNumber": 108 }, "deprecated": false, "initialIsOpen": false @@ -3293,7 +3403,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 104 + "lineNumber": 105 }, "deprecated": false, "initialIsOpen": false @@ -3307,7 +3417,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 106 + "lineNumber": 107 }, "deprecated": false, "initialIsOpen": false @@ -3321,7 +3431,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 105 + "lineNumber": 106 }, "deprecated": false, "initialIsOpen": false @@ -3338,7 +3448,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 265 + "lineNumber": 266 }, "deprecated": false, "initialIsOpen": false @@ -3355,7 +3465,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 187 + "lineNumber": 188 }, "deprecated": false, "initialIsOpen": false @@ -3372,7 +3482,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 191 + "lineNumber": 192 }, "deprecated": false, "initialIsOpen": false @@ -3389,7 +3499,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 113 + "lineNumber": 114 }, "deprecated": false, "initialIsOpen": false @@ -3405,7 +3515,7 @@ "description": [], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 151 + "lineNumber": 152 }, "deprecated": false, "children": [ @@ -3421,7 +3531,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 152 + "lineNumber": 153 }, "deprecated": false }, @@ -3437,7 +3547,7 @@ ], "source": { "path": "x-pack/plugins/maps/common/constants.ts", - "lineNumber": 153 + "lineNumber": 154 }, "deprecated": false } diff --git a/api_docs/observability.json b/api_docs/observability.json index a80e8e44fe665e..fc645dd7e2e6b8 100644 --- a/api_docs/observability.json +++ b/api_docs/observability.json @@ -170,7 +170,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 34 + "lineNumber": 36 }, "deprecated": false, "children": [ @@ -186,7 +186,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 34 + "lineNumber": 36 }, "deprecated": false, "isRequired": true @@ -283,7 +283,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 14 + "lineNumber": 16 }, "deprecated": false, "children": [ @@ -299,7 +299,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 14 + "lineNumber": 16 }, "deprecated": false, "isRequired": true @@ -322,7 +322,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 24 + "lineNumber": 26 }, "deprecated": false, "children": [ @@ -338,7 +338,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/components/shared/index.tsx", - "lineNumber": 24 + "lineNumber": 26 }, "deprecated": false, "isRequired": true @@ -1050,7 +1050,7 @@ "label": "unsafe", "description": [], "signature": [ - "{ alertingExperience: { enabled: boolean; }; }" + "{ alertingExperience: { enabled: boolean; }; cases: { enabled: boolean; }; }" ], "source": { "path": "x-pack/plugins/observability/public/index.ts", @@ -2040,7 +2040,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 40 + "lineNumber": 42 }, "deprecated": false, "children": [ @@ -2062,7 +2062,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 41 + "lineNumber": 43 }, "deprecated": false }, @@ -2084,7 +2084,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 42 + "lineNumber": 44 }, "deprecated": false }, @@ -2107,7 +2107,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 43 + "lineNumber": 45 }, "deprecated": false } @@ -2123,7 +2123,7 @@ "description": [], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 46 + "lineNumber": 48 }, "deprecated": false, "children": [ @@ -2146,7 +2146,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 47 + "lineNumber": 49 }, "deprecated": false }, @@ -2168,7 +2168,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 48 + "lineNumber": 50 }, "deprecated": false }, @@ -2190,7 +2190,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 49 + "lineNumber": 51 }, "deprecated": false }, @@ -2212,7 +2212,7 @@ ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 50 + "lineNumber": 52 }, "deprecated": false } @@ -2838,7 +2838,7 @@ "METRIC_TYPE" ], "source": { - "path": "node_modules/@kbn/analytics/target/types/metrics/index.d.ts", + "path": "node_modules/@kbn/analytics/target_types/metrics/index.d.ts", "lineNumber": 10 }, "deprecated": false, @@ -3142,11 +3142,15 @@ "section": "def-public.AlertTypeModel", "text": "AlertTypeModel" }, - " & { format: Formatter; }) => void; getFormatter: (typeId: string) => Formatter | undefined; }; isAlertingExperienceEnabled: () => boolean; }" + " & { format: Formatter; }) => void; getFormatter: (typeId: string) => Formatter | undefined; }; isAlertingExperienceEnabled: () => boolean; navigation: { registerSections: (sections$: ", + "Observable", + "<", + "NavigationSection", + "[]>) => void; }; }" ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 38 + "lineNumber": 40 }, "deprecated": false, "lifecycle": "setup", @@ -3160,11 +3164,13 @@ "label": "ObservabilityPublicStart", "description": [], "signature": [ - "void" + "{ navigation: { PageTemplate: (pageTemplateProps: ", + "WrappedPageTemplateProps", + ") => JSX.Element; }; }" ], "source": { "path": "x-pack/plugins/observability/public/plugin.ts", - "lineNumber": 53 + "lineNumber": 55 }, "deprecated": false, "lifecycle": "start", @@ -3431,7 +3437,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 25 + "lineNumber": 35 }, "deprecated": false, "children": [ @@ -3447,7 +3453,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 25 + "lineNumber": 35 }, "deprecated": false, "isRequired": false @@ -3470,7 +3476,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 11 + "lineNumber": 21 }, "deprecated": false, "children": [ @@ -3486,7 +3492,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 11 + "lineNumber": 21 }, "deprecated": false, "isRequired": false @@ -3503,7 +3509,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 11 + "lineNumber": 21 }, "deprecated": false, "isRequired": false @@ -3520,7 +3526,7 @@ ], "source": { "path": "x-pack/plugins/observability/server/utils/queries.ts", - "lineNumber": 11 + "lineNumber": 21 }, "deprecated": false, "isRequired": true @@ -3861,7 +3867,15 @@ "Type", "; end: ", "Type", - "; }>, ", + "; status: ", + "UnionC", + "<[", + "LiteralC", + "<\"all\">, ", + "LiteralC", + "<\"open\">, ", + "LiteralC", + "<\"closed\">]>; }>, ", "PartialC", "<{ kuery: ", "StringC", @@ -3928,11 +3942,11 @@ "label": "ObservabilityConfig", "description": [], "signature": [ - "{ readonly enabled: boolean; readonly annotations: Readonly<{} & { enabled: boolean; index: string; }>; readonly unsafe: Readonly<{} & { alertingExperience: Readonly<{} & { enabled: boolean; }>; }>; }" + "{ readonly enabled: boolean; readonly annotations: Readonly<{} & { enabled: boolean; index: string; }>; readonly unsafe: Readonly<{} & { cases: Readonly<{} & { enabled: boolean; }>; alertingExperience: Readonly<{} & { enabled: boolean; }>; }>; }" ], "source": { "path": "x-pack/plugins/observability/server/index.ts", - "lineNumber": 34 + "lineNumber": 35 }, "deprecated": false, "initialIsOpen": false @@ -3974,7 +3988,15 @@ "Type", "; end: ", "Type", - "; }>, ", + "; status: ", + "UnionC", + "<[", + "LiteralC", + "<\"all\">, ", + "LiteralC", + "<\"open\">, ", + "LiteralC", + "<\"closed\">]>; }>, ", "PartialC", "<{ kuery: ", "StringC", diff --git a/api_docs/presentation_util.json b/api_docs/presentation_util.json index ee045dfd830fbe..bde8373e4450e0 100644 --- a/api_docs/presentation_util.json +++ b/api_docs/presentation_util.json @@ -528,7 +528,9 @@ "type": "CompoundType", "tags": [], "label": "Component", - "description": [], + "description": [ + "A component deferred by `React.lazy`" + ], "signature": [ "React.ComponentType

" ], @@ -545,7 +547,9 @@ "type": "CompoundType", "tags": [], "label": "fallback", - "description": [], + "description": [ + "A fallback component to render while things load; default is `EuiLoadingSpinner`" + ], "signature": [ "React.ReactElement React.ReactElement React.Component)> | null) | (new (props: any) => React.Component)> | null" ], diff --git a/api_docs/reporting.json b/api_docs/reporting.json index e8cea57b24902c..b2529f510bc2ed 100644 --- a/api_docs/reporting.json +++ b/api_docs/reporting.json @@ -911,10 +911,10 @@ "children": [ { "parentPluginId": "reporting", - "id": "def-server.ReportingCore.getStartContract", + "id": "def-server.ReportingCore.getContract", "type": "Function", "tags": [], - "label": "getStartContract", + "label": "getContract", "description": [], "signature": [ "() => ", @@ -1010,7 +1010,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 92 + "lineNumber": 90 }, "deprecated": false, "children": [ @@ -1026,7 +1026,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 92 + "lineNumber": 90 }, "deprecated": false, "isRequired": true @@ -1048,7 +1048,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 106 + "lineNumber": 104 }, "deprecated": false, "children": [ @@ -1064,7 +1064,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 106 + "lineNumber": 104 }, "deprecated": false, "isRequired": true @@ -1084,7 +1084,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 119 + "lineNumber": 117 }, "deprecated": false, "children": [], @@ -1102,7 +1102,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 130 + "lineNumber": 128 }, "deprecated": false, "children": [], @@ -1120,7 +1120,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 137 + "lineNumber": 135 }, "deprecated": false, "children": [], @@ -1146,7 +1146,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 144 + "lineNumber": 142 }, "deprecated": false, "children": [ @@ -1168,7 +1168,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 144 + "lineNumber": 142 }, "deprecated": false, "isRequired": true @@ -1190,7 +1190,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 153 + "lineNumber": 151 }, "deprecated": false, "children": [], @@ -1215,7 +1215,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 187 + "lineNumber": 185 }, "deprecated": false, "children": [], @@ -1233,7 +1233,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 199 + "lineNumber": 197 }, "deprecated": false, "children": [], @@ -1253,7 +1253,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 206 + "lineNumber": 204 }, "deprecated": false, "children": [], @@ -1272,7 +1272,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 214 + "lineNumber": 212 }, "deprecated": false, "children": [], @@ -1302,7 +1302,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 218 + "lineNumber": 216 }, "deprecated": false, "children": [ @@ -1321,7 +1321,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 218 + "lineNumber": 216 }, "deprecated": false, "isRequired": true @@ -1343,7 +1343,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 222 + "lineNumber": 220 }, "deprecated": false, "children": [], @@ -1363,7 +1363,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 226 + "lineNumber": 224 }, "deprecated": false, "children": [], @@ -1383,7 +1383,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 236 + "lineNumber": 234 }, "deprecated": false, "children": [], @@ -1401,7 +1401,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 242 + "lineNumber": 240 }, "deprecated": false, "children": [], @@ -1420,7 +1420,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 250 + "lineNumber": 248 }, "deprecated": false, "children": [], @@ -1454,7 +1454,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 262 + "lineNumber": 260 }, "deprecated": false, "children": [ @@ -1478,7 +1478,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 262 + "lineNumber": 260 }, "deprecated": false, "isRequired": true @@ -1508,7 +1508,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 268 + "lineNumber": 266 }, "deprecated": false, "children": [ @@ -1531,7 +1531,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 268 + "lineNumber": 266 }, "deprecated": false, "isRequired": true @@ -1548,7 +1548,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 268 + "lineNumber": 266 }, "deprecated": false, "isRequired": true @@ -1578,7 +1578,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 282 + "lineNumber": 280 }, "deprecated": false, "children": [ @@ -1594,7 +1594,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 282 + "lineNumber": 280 }, "deprecated": false, "isRequired": true @@ -1611,7 +1611,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 282 + "lineNumber": 280 }, "deprecated": false, "isRequired": false @@ -1628,7 +1628,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 282 + "lineNumber": 280 }, "deprecated": false, "isRequired": true @@ -1666,7 +1666,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 302 + "lineNumber": 300 }, "deprecated": false, "children": [ @@ -1689,7 +1689,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 302 + "lineNumber": 300 }, "deprecated": false, "isRequired": true @@ -1706,7 +1706,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 302 + "lineNumber": 300 }, "deprecated": false, "isRequired": true @@ -1734,7 +1734,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 312 + "lineNumber": 310 }, "deprecated": false, "children": [], @@ -1760,7 +1760,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 317 + "lineNumber": 315 }, "deprecated": false, "children": [], @@ -1778,7 +1778,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 322 + "lineNumber": 320 }, "deprecated": false, "children": [ @@ -1794,7 +1794,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 322 + "lineNumber": 320 }, "deprecated": false, "isRequired": true @@ -1814,7 +1814,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 326 + "lineNumber": 324 }, "deprecated": false, "children": [ @@ -1830,7 +1830,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 326 + "lineNumber": 324 }, "deprecated": false, "isRequired": true @@ -1850,7 +1850,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 330 + "lineNumber": 328 }, "deprecated": false, "children": [], @@ -1918,7 +1918,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 25 + "lineNumber": 26 }, "deprecated": false, "children": [ @@ -1934,7 +1934,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 30 + "lineNumber": 31 }, "deprecated": false, "children": [ @@ -1959,7 +1959,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 30 + "lineNumber": 31 }, "deprecated": false, "isRequired": true @@ -2002,7 +2002,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 34 + "lineNumber": 35 }, "deprecated": false, "children": [ @@ -2025,7 +2025,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 34 + "lineNumber": 35 }, "deprecated": false, "isRequired": true @@ -2048,7 +2048,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 34 + "lineNumber": 35 }, "deprecated": false, "isRequired": true @@ -2091,7 +2091,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 87 + "lineNumber": 88 }, "deprecated": false, "children": [ @@ -2113,7 +2113,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 87 + "lineNumber": 88 }, "deprecated": false, "isRequired": true @@ -2136,7 +2136,7 @@ ], "source": { "path": "x-pack/plugins/reporting/server/plugin.ts", - "lineNumber": 87 + "lineNumber": 88 }, "deprecated": false, "isRequired": true diff --git a/api_docs/rule_registry.json b/api_docs/rule_registry.json index 63a50f8fbd42f5..cd1f4994c1da61 100644 --- a/api_docs/rule_registry.json +++ b/api_docs/rule_registry.json @@ -695,8 +695,8 @@ "{ readonly enabled: boolean; readonly index: string; readonly write: Readonly<{} & { enabled: boolean; }>; }" ], "source": { - "path": "x-pack/plugins/rule_registry/server/index.ts", - "lineNumber": 28 + "path": "x-pack/plugins/rule_registry/server/config.ts", + "lineNumber": 20 }, "deprecated": false, "initialIsOpen": false @@ -706,18 +706,49 @@ "setup": { "parentPluginId": "ruleRegistry", "id": "def-server.RuleRegistryPluginSetupContract", - "type": "Type", + "type": "Interface", "tags": [], "label": "RuleRegistryPluginSetupContract", "description": [], - "signature": [ - "RuleDataPluginService" - ], "source": { "path": "x-pack/plugins/rule_registry/server/plugin.ts", - "lineNumber": 12 + "lineNumber": 22 }, "deprecated": false, + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.RuleRegistryPluginSetupContract.ruleDataService", + "type": "Object", + "tags": [], + "label": "ruleDataService", + "description": [], + "signature": [ + "RuleDataPluginService" + ], + "source": { + "path": "x-pack/plugins/rule_registry/server/plugin.ts", + "lineNumber": 23 + }, + "deprecated": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.RuleRegistryPluginSetupContract.eventLogService", + "type": "Object", + "tags": [], + "label": "eventLogService", + "description": [], + "signature": [ + "IEventLogService" + ], + "source": { + "path": "x-pack/plugins/rule_registry/server/plugin.ts", + "lineNumber": 24 + }, + "deprecated": false + } + ], "lifecycle": "setup", "initialIsOpen": true }, @@ -733,7 +764,7 @@ ], "source": { "path": "x-pack/plugins/rule_registry/server/plugin.ts", - "lineNumber": 13 + "lineNumber": 27 }, "deprecated": false, "lifecycle": "start", diff --git a/api_docs/spaces.json b/api_docs/spaces.json index 5225e8cebbeb5a..2f2d79c2379d58 100644 --- a/api_docs/spaces.json +++ b/api_docs/spaces.json @@ -1210,7 +1210,7 @@ "plugin": "reporting", "link": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 271 + "lineNumber": 269 } }, { @@ -2030,21 +2030,21 @@ "plugin": "reporting", "link": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 269 + "lineNumber": 267 } }, { "plugin": "reporting", "link": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 291 + "lineNumber": 289 } }, { "plugin": "reporting", "link": { "path": "x-pack/plugins/reporting/server/core.ts", - "lineNumber": 303 + "lineNumber": 301 } }, { diff --git a/api_docs/triggers_actions_ui.json b/api_docs/triggers_actions_ui.json index cade40cf39c738..de7a13b3080fe0 100644 --- a/api_docs/triggers_actions_ui.json +++ b/api_docs/triggers_actions_ui.json @@ -45,7 +45,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 77 + "lineNumber": 80 }, "deprecated": false, "children": [ @@ -61,7 +61,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 88 + "lineNumber": 91 }, "deprecated": false, "children": [], @@ -94,7 +94,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 93 + "lineNumber": 96 }, "deprecated": false, "children": [ @@ -117,7 +117,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 93 + "lineNumber": 96 }, "deprecated": false, "isRequired": true @@ -134,7 +134,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 93 + "lineNumber": 96 }, "deprecated": false, "isRequired": true @@ -161,7 +161,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 169 + "lineNumber": 172 }, "deprecated": false, "children": [], @@ -179,7 +179,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 203 + "lineNumber": 206 }, "deprecated": false, "children": [], @@ -228,44 +228,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.AlertAdd", - "type": "Function", - "tags": [], - "label": "AlertAdd", - "description": [], - "signature": [ - "(props: ", - "AlertAddProps", - ">) => JSX.Element" - ], - "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/index.tsx", - "lineNumber": 11 - }, - "deprecated": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.props", - "type": "Uncategorized", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "T" - ], - "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx", - "lineNumber": 16 - }, - "deprecated": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "triggersActionsUi", "id": "def-public.AlertConditions", @@ -346,44 +308,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.AlertEdit", - "type": "Function", - "tags": [], - "label": "AlertEdit", - "description": [], - "signature": [ - "(props: ", - "AlertEditProps", - ">) => JSX.Element" - ], - "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx", - "lineNumber": 17 - }, - "deprecated": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "triggersActionsUi", - "id": "def-public.props", - "type": "Uncategorized", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "T" - ], - "source": { - "path": "x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx", - "lineNumber": 16 - }, - "deprecated": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "triggersActionsUi", "id": "def-public.ConnectorAddFlyout", @@ -518,7 +442,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx", - "lineNumber": 63 + "lineNumber": 62 }, "deprecated": false, "children": [], @@ -545,7 +469,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx", - "lineNumber": 68 + "lineNumber": 67 }, "deprecated": false, "children": [], @@ -752,7 +676,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx", - "lineNumber": 64 + "lineNumber": 63 }, "deprecated": false, "children": [], @@ -779,7 +703,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx", - "lineNumber": 99 + "lineNumber": 98 }, "deprecated": false, "children": [], @@ -1459,7 +1383,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 234 + "lineNumber": 235 }, "deprecated": false, "children": [ @@ -1472,7 +1396,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 235 + "lineNumber": 236 }, "deprecated": false }, @@ -1485,7 +1409,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 236 + "lineNumber": 237 }, "deprecated": false }, @@ -1498,7 +1422,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 237 + "lineNumber": 238 }, "deprecated": false }, @@ -1522,7 +1446,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 238 + "lineNumber": 239 }, "deprecated": false }, @@ -1545,7 +1469,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 239 + "lineNumber": 240 }, "deprecated": false, "returnComment": [], @@ -1562,7 +1486,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 239 + "lineNumber": 240 }, "deprecated": false } @@ -1588,7 +1512,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 240 + "lineNumber": 241 }, "deprecated": false, "returnComment": [], @@ -1620,7 +1544,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 243 + "lineNumber": 244 }, "deprecated": false }, @@ -1636,7 +1560,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 244 + "lineNumber": 245 }, "deprecated": false } @@ -1662,7 +1586,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 212 + "lineNumber": 213 }, "deprecated": false, "children": [ @@ -1678,7 +1602,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 217 + "lineNumber": 218 }, "deprecated": false }, @@ -1691,7 +1615,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 218 + "lineNumber": 219 }, "deprecated": false }, @@ -1704,7 +1628,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 219 + "lineNumber": 220 }, "deprecated": false }, @@ -1720,7 +1644,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 220 + "lineNumber": 221 }, "deprecated": false }, @@ -1736,7 +1660,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 221 + "lineNumber": 222 }, "deprecated": false, "returnComment": [], @@ -1753,7 +1677,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 221 + "lineNumber": 222 }, "deprecated": false }, @@ -1769,7 +1693,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 221 + "lineNumber": 222 }, "deprecated": false } @@ -1795,7 +1719,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 222 + "lineNumber": 223 }, "deprecated": false, "returnComment": [], @@ -1812,7 +1736,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 223 + "lineNumber": 224 }, "deprecated": false }, @@ -1836,7 +1760,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 224 + "lineNumber": 225 }, "deprecated": false } @@ -1860,7 +1784,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 226 + "lineNumber": 227 }, "deprecated": false }, @@ -1873,7 +1797,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 227 + "lineNumber": 228 }, "deprecated": false }, @@ -1896,7 +1820,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 228 + "lineNumber": 229 }, "deprecated": false }, @@ -1912,7 +1836,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 229 + "lineNumber": 230 }, "deprecated": false }, @@ -1934,7 +1858,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 230 + "lineNumber": 231 }, "deprecated": false }, @@ -1956,7 +1880,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 231 + "lineNumber": 232 }, "deprecated": false } @@ -2098,7 +2022,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 247 + "lineNumber": 248 }, "deprecated": false, "children": [ @@ -2114,7 +2038,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 248 + "lineNumber": 249 }, "deprecated": false } @@ -2483,7 +2407,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 127 + "lineNumber": 128 }, "deprecated": false, "children": [ @@ -2499,7 +2423,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 128 + "lineNumber": 129 }, "deprecated": false } @@ -2531,7 +2455,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 79 + "lineNumber": 80 }, "deprecated": false, "initialIsOpen": false @@ -2581,7 +2505,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 161 + "lineNumber": 162 }, "deprecated": false, "initialIsOpen": false @@ -2637,7 +2561,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 64 + "lineNumber": 65 }, "deprecated": false, "initialIsOpen": false @@ -2654,7 +2578,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 179 + "lineNumber": 180 }, "deprecated": false, "initialIsOpen": false @@ -2695,7 +2619,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 40 + "lineNumber": 41 }, "deprecated": false, "initialIsOpen": false @@ -2753,7 +2677,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", - "lineNumber": 68 + "lineNumber": 69 }, "deprecated": false, "initialIsOpen": false @@ -3782,7 +3706,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 41 + "lineNumber": 44 }, "deprecated": false, "children": [ @@ -3801,7 +3725,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 42 + "lineNumber": 45 }, "deprecated": false }, @@ -3826,7 +3750,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 43 + "lineNumber": 46 }, "deprecated": false } @@ -3843,7 +3767,7 @@ "description": [], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 46 + "lineNumber": 49 }, "deprecated": false, "children": [ @@ -3862,7 +3786,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 47 + "lineNumber": 50 }, "deprecated": false }, @@ -3887,7 +3811,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 48 + "lineNumber": 51 }, "deprecated": false }, @@ -3907,7 +3831,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 49 + "lineNumber": 52 }, "deprecated": false, "returnComment": [], @@ -3948,7 +3872,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 50 + "lineNumber": 53 }, "deprecated": false } @@ -3970,7 +3894,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 52 + "lineNumber": 55 }, "deprecated": false, "returnComment": [], @@ -4005,7 +3929,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 53 + "lineNumber": 56 }, "deprecated": false } @@ -4027,7 +3951,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 55 + "lineNumber": 58 }, "deprecated": false, "returnComment": [], @@ -4060,7 +3984,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 56 + "lineNumber": 59 }, "deprecated": false } @@ -4082,7 +4006,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 58 + "lineNumber": 61 }, "deprecated": false, "returnComment": [], @@ -4115,7 +4039,7 @@ ], "source": { "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", - "lineNumber": 59 + "lineNumber": 62 }, "deprecated": false } diff --git a/api_docs/visualizations.json b/api_docs/visualizations.json index bea890f00e0ed2..0df428a158a3da 100644 --- a/api_docs/visualizations.json +++ b/api_docs/visualizations.json @@ -85,7 +85,15 @@ "label": "getSupportedTriggers", "description": [], "signature": [ - "(() => string[]) | undefined" + "((params?: ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + " | undefined) => string[]) | undefined" ], "source": { "path": "src/plugins/visualizations/public/vis_types/base_vis_type.ts", @@ -3327,7 +3335,15 @@ "\nIf given, it will return the supported triggers for this vis." ], "signature": [ - "(() => string[]) | undefined" + "((params?: ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + " | undefined) => string[]) | undefined" ], "source": { "path": "src/plugins/visualizations/public/vis_types/types.ts", diff --git a/dev_docs/tutorials/expressions.mdx b/dev_docs/tutorials/expressions.mdx new file mode 100644 index 00000000000000..f0fc1dc595cfa4 --- /dev/null +++ b/dev_docs/tutorials/expressions.mdx @@ -0,0 +1,129 @@ +--- +id: kibDevTutorialExpressions +slug: /kibana-dev-docs/tutorials/expressions +title: Kibana Expressions Service +summary: Kibana Expressions Service +date: 2021-06-01 +tags: ['kibana', 'onboarding', 'dev', 'architecture'] +--- + +## Expressions service + +Expression service exposes a registry of reusable functions primary used for fetching and transposing data and a registry of renderer functions that can render data into a DOM element. +Adding functions is easy and so is reusing them. An expression is a chain of functions with provided arguments, which given a single input translates to a single output. +Each expression is representable by a human friendly string which a user can type. + +### creating expressions + +Here is a very simple expression string: + + essql 'select column1, column2 from myindex' | mapColumn name=column3 fn='{ column1 + 3 }' | table + + +It consists of 3 functions: + + - essql which runs given sql query against elasticsearch and returns the results + - `mapColumn`, which computes a new column from existing ones; + - `table`, which prepares the data for rendering in a tabular format. + +The same expression could also be constructed in the code: + +```ts +import { buildExpression, buildExpressionFunction } from 'src/plugins/expressions'; + +const expression = buildExpression([ + buildExpressionFunction('essql', [ q: 'select column1, column2 from myindex' ]), + buildExpressionFunction('mapColumn', [ name: 'column3', expression: 'column1 + 3' ]), + buildExpressionFunction('table'), +] +``` + +Note: Consumers need to be aware which plugin registers specific functions with expressions function registry and import correct type definitions from there. + + + The `expressions` service is available on both server and client, with similar APIs. + + +### Running expressions + +Expression service exposes `execute` method which allows you to execute an expression: + +```ts +const executionContract = expressions.execute(expression, input); +const result = await executionContract.getData(); +``` + + + Check the full spec of execute function [here](https://github.com/elastic/kibana/blob/master/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md) + + +In addition, on the browser side, there are two additional ways to run expressions and render the results. + +#### React expression renderer component + +This is the easiest way to get expressions rendered inside your application. + +```ts + +``` + + + Check the full spec of ReactExpressionRenderer component props [here](https://github.com/elastic/kibana/blob/master/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) + + +#### Expression loader + +If you are not using React, you can use the loader expression service provides to achieve the same: + +```ts +const handler = loader(domElement, expression, params); +``` + + + Check the full spec of expression loader params [here](https://github.com/elastic/kibana/blob/master/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) + + +### Creating new expression functions + +Creating a new expression function is easy, just call `registerFunction` method on expressions service setup contract with your function definition: + +```ts +const functionDefinition = { + name: 'clog', + args: {}, + help: 'Outputs the context to the console', + fn: (input: unknown) => { + // eslint-disable-next-line no-console + console.log(input); + return input; + }, +}; + +expressions.registerFunction(functionDefinition); +``` + + + Check the full interface of ExpressionFuntionDefinition [here](https://github.com/elastic/kibana/blob/master/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinition.md) + + +### Creating new expression renderers + +Adding new renderers is just as easy as adding functions: + +```ts +const rendererDefinition = { + name: 'debug', + help: 'Outputs the context to the dom element', + render: (domElement, input, handlers) => { + // eslint-disable-next-line no-console + domElement.innerText = JSON.strinfigy(input); + handlers.done(); + }, +}; + +expressions.registerRenderer(rendererDefinition); +``` + + + Check the full interface of ExpressionRendererDefinition [here](https://github.com/elastic/kibana/blob/master/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderdefinition.md) + diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 4e8bbf76eaacb0..dbfbe90ec9263e 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -80,6 +80,7 @@ yarn kbn watch-bazel - @kbn/eslint-plugin-eslint - @kbn/expect - @kbn/i18n +- @kbn/io-ts-utils - @kbn/legacy-logging - @kbn/logging - @kbn/mapbox-gl diff --git a/docs/user/alerting/rule-management.asciidoc b/docs/user/alerting/rule-management.asciidoc index e47858f58cd1a6..b908bd03b09927 100644 --- a/docs/user/alerting/rule-management.asciidoc +++ b/docs/user/alerting/rule-management.asciidoc @@ -57,15 +57,6 @@ These operations can also be performed in bulk by multi-selecting rules and clic [role="screenshot"] image:images/bulk-mute-disable.png[The Manage rules button lets you mute/unmute, enable/disable, and delete in bulk] -[float] -[[importing-and-exporting-rules]] -=== Importing and exporting rules - -To import and export rules, use the <>. -After the succesful import the proper banner will be displayed: -[role="screenshot"] -image::images/rules-imported-banner.png[Rules import banner, width=50%] - [float] === Required permissions diff --git a/docs/user/dashboard/images/lens_missing_values_strategy.png b/docs/user/dashboard/images/lens_missing_values_strategy.png new file mode 100644 index 00000000000000..d77c230b533f58 Binary files /dev/null and b/docs/user/dashboard/images/lens_missing_values_strategy.png differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index 613432908df3d3..c5718b2a089bfc 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -4,8 +4,19 @@ To create visualizations with *Lens*, you drag and drop data fields onto the visualization builder, then *Lens* uses heuristics to apply each field. -[role="screenshot"] -image:dashboard/images/lens.png[Lens] +++++ + + +
+++++ [float] [[lens-required-choices]] @@ -179,6 +190,8 @@ A subset of *Lens* visualizations support value labels. [role="screenshot"] image::images/lens_value_labels_xychart_toggle.png[Lens Bar chart value labels menu] +NOTE: In bar charts, you are unable to move the label positions. + * *Pie*, *Donut*, and *Treemap* + [role="screenshot"] @@ -232,6 +245,9 @@ refer to <>. Sorting dimensions in visualizations is unsupported in *Lens*. +You can sort the dimensions for a single column in data tables: click the column header, then select the sorting criteria you want to use. +If you use the dimension as `Columns`, then all the columns that belong to the same dimension are sorted in the table. + [float] [[is-it-possible-to-use-saved-serches-in-lens]] ===== How do I visualize saved searches? @@ -243,3 +259,47 @@ Visualizing saved searches in unsupported in *Lens*. ===== How do I change the number of suggestions? Configuring the *Suggestions* that *Lens* automatically populates is unsupported. + +[float] +[[is-it-possible-to-use-different-indexpatterns-in-lens]] +===== Can I visualize multiple index patterns in a single visualization? + +You can create *Bar*, *Line* and *Area* charts from multiple index patterns. + +Each *Layer* in a visualization is associated with an index pattern and mutiple *Layers* can be combined together within the same visualization. Each *Layer* also has a chart switcher button in order to select the best type of visualization for the specific dataset. +You can also change the index pattern for a single *Layer*. + +[float] +[[why-my-field-x-is-missing-from-the-fields-list]] +===== Why is my field X missing from the fields list? + +*Lens* does not support the visualization of full-text fields, therefore it is not showing them in the data summary. + +[float] +[[how-to-handle-gaps-in-time-series-visualizations]] +===== How do I handle gaps in time series visualizations? + +*Lens* provides a set of features to handle missing values for *Area* and *Line* charts, which is useful for sparse data in time series data. + +To select a different way to represent missing values, open the *Visual options* menu, then select how to handle missing values. The default is to hide the missing values. ++ +[role="screenshot"] +image::images/lens_missing_values_strategy.png[Lens Missing values strategies menu] + +[float] +[[is-it-possible-to-change-the-scale-of-Y-axis]] +===== Is it possible to statically define the scale of the y-axis in a visualization? + +The ability to start the y-axis from another value than 0, or use a logarithmic scale, is unsupported in *Lens*. + +[float] +[[is-it-possible-to-have-pagination-for-datatable]] +===== Is it possible to have pagination in a data table? + +Pagination in a data table is unsupported in *Lens*. However, the <> supports pagination. + +[float] +[[is-it-possible-to-have-more-than-one-Y-axis-scale]] +===== Is it possible to have more than one y-axis scale in visualizations? + +*Lens* lets you pick, for each Y dimension, up to two distinct axis: *left* and *right*. Each axis can have a different scale. diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 8a0dd5e4e2a2ba..38435708aaf997 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -20,6 +20,20 @@ Manage your indices and ingest pipelines, monitor the health of your Elastic Stack cluster, and control which users have access to which features. +++++ + + +
+++++ + *{kib} is for administrators, analysts, and business users.* As an admin, your role is to manage the Elastic Stack, from creating your deployment to getting {es} data into {kib}, and then @@ -43,9 +57,6 @@ If you’re not ready to use your own data, you can add a sample data set. The home page provides access to the *Enterprise Search*, *Observability*, and *Security* solutions, and everything you need to visualize and analyze your data. -[role="screenshot"] -image::images/home-page.png[Kibana home page] - To access all of {kib} features, use the main menu. Open this menu by clicking the menu icon. To keep the main menu visible at all times, click *Dock navigation*. diff --git a/package.json b/package.json index 627e8abd9d259c..e5b9ca1ef98cc6 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl/npm_module", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n/npm_module", "@kbn/interpreter": "link:packages/kbn-interpreter", - "@kbn/io-ts-utils": "link:packages/kbn-io-ts-utils", + "@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils/npm_module", "@kbn/legacy-logging": "link:bazel-bin/packages/kbn-legacy-logging/npm_module", "@kbn/logging": "link:bazel-bin/packages/kbn-logging/npm_module", "@kbn/monaco": "link:bazel-bin/packages/kbn-monaco/npm_module", @@ -229,6 +229,7 @@ "expiry-js": "0.1.7", "extract-zip": "^2.0.1", "fast-deep-equal": "^3.1.1", + "fflate": "^0.6.9", "file-saver": "^1.3.8", "file-type": "^10.9.0", "focus-trap-react": "^3.1.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index c885666f7a916e..de3498da1a6976 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -22,6 +22,7 @@ filegroup( "//packages/kbn-eslint-plugin-eslint:build", "//packages/kbn-expect:build", "//packages/kbn-i18n:build", + "//packages/kbn-io-ts-utils:build", "//packages/kbn-legacy-logging:build", "//packages/kbn-logging:build", "//packages/kbn-mapbox-gl:build", diff --git a/packages/kbn-docs-utils/src/api_docs/build_api_declarations/build_arrow_fn_dec.ts b/packages/kbn-docs-utils/src/api_docs/build_api_declarations/build_arrow_fn_dec.ts index bcbb9cf3e7bbe0..c714165a0922c0 100644 --- a/packages/kbn-docs-utils/src/api_docs/build_api_declarations/build_arrow_fn_dec.ts +++ b/packages/kbn-docs-utils/src/api_docs/build_api_declarations/build_arrow_fn_dec.ts @@ -19,7 +19,7 @@ import { import { AnchorLink, ApiDeclaration, TypeKind } from '../types'; import { buildApiDecsForParameters } from './build_parameter_decs'; import { getSignature } from './get_signature'; -import { getJSDocReturnTagComment } from './js_doc_utils'; +import { getJSDocReturnTagComment, getJSDocs } from './js_doc_utils'; import { buildBasicApiDeclaration } from './build_basic_api_declaration'; /** @@ -66,7 +66,8 @@ export function getArrowFunctionDec( anchorLink, currentPluginId, log, - captureReferences + captureReferences, + getJSDocs(node) ), // need to override the signature - use the initializer, not the node. signature: getSignature(initializer, plugins, log), diff --git a/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts b/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts index cdf1e5b718ccaf..ff71b0efc79d1c 100644 --- a/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts +++ b/packages/kbn-docs-utils/src/api_docs/tests/api_doc_suite.test.ts @@ -46,6 +46,8 @@ function fnIsCorrect(fn: ApiDeclaration | undefined) { expect(p1!.isRequired).toBe(true); expect(p1!.signature?.length).toBe(1); expect(linkCount(p1!.signature!)).toBe(0); + expect(p1?.description).toBeDefined(); + expect(p1?.description?.length).toBe(1); const p2 = fn?.children!.find((c) => c.label === 'b'); expect(p2).toBeDefined(); @@ -53,12 +55,15 @@ function fnIsCorrect(fn: ApiDeclaration | undefined) { expect(p2!.type).toBe(TypeKind.NumberKind); expect(p2!.signature?.length).toBe(1); expect(linkCount(p2!.signature!)).toBe(0); + expect(p2?.description?.length).toBe(1); const p3 = fn?.children!.find((c) => c.label === 'c'); expect(p3).toBeDefined(); expect(p3!.isRequired).toBe(true); expect(p3!.type).toBe(TypeKind.ArrayKind); expect(linkCount(p3!.signature!)).toBe(1); + expect(p3?.description).toBeDefined(); + expect(p3?.description?.length).toBe(1); const p4 = fn?.children!.find((c) => c.label === 'd'); expect(p4).toBeDefined(); @@ -66,6 +71,7 @@ function fnIsCorrect(fn: ApiDeclaration | undefined) { expect(p4!.type).toBe(TypeKind.CompoundTypeKind); expect(p4!.signature?.length).toBe(1); expect(linkCount(p4!.signature!)).toBe(1); + expect(p4?.description?.length).toBe(1); const p5 = fn?.children!.find((c) => c.label === 'e'); expect(p5).toBeDefined(); @@ -73,6 +79,7 @@ function fnIsCorrect(fn: ApiDeclaration | undefined) { expect(p5!.type).toBe(TypeKind.StringKind); expect(p5!.signature?.length).toBe(1); expect(linkCount(p5!.signature!)).toBe(0); + expect(p5?.description?.length).toBe(1); } beforeAll(() => { diff --git a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json index deb40d875b4428..7d8a90c3aad77a 100644 --- a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json +++ b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json @@ -165,7 +165,9 @@ "type": "CompoundType", "tags": [], "label": "a", - "description": [], + "description": [ + "im a string" + ], "signature": [ { "pluginId": "pluginA", @@ -292,7 +294,9 @@ "type": "string", "tags": [], "label": "a", - "description": [], + "description": [ + "The letter A" + ], "signature": [ "string" ], @@ -309,7 +313,9 @@ "type": "number", "tags": [], "label": "b", - "description": [], + "description": [ + "Feed me to the function" + ], "signature": [ "number | undefined" ], @@ -326,7 +332,9 @@ "type": "Array", "tags": [], "label": "c", - "description": [], + "description": [ + "So many params" + ], "signature": [ { "pluginId": "pluginA", @@ -350,7 +358,9 @@ "type": "CompoundType", "tags": [], "label": "d", - "description": [], + "description": [ + "a great param" + ], "signature": [ { "pluginId": "pluginA", @@ -373,7 +383,9 @@ "type": "string", "tags": [], "label": "e", - "description": [], + "description": [ + "Another comment" + ], "signature": [ "string | undefined" ], @@ -586,7 +598,7 @@ "section": "def-public.ImAType", "text": "ImAType" }, - ", e: string | undefined) => ", + ", e?: string | undefined) => ", { "pluginId": "pluginA", "scope": "public", diff --git a/packages/kbn-io-ts-utils/BUILD.bazel b/packages/kbn-io-ts-utils/BUILD.bazel new file mode 100644 index 00000000000000..6b26173fe8f369 --- /dev/null +++ b/packages/kbn-io-ts-utils/BUILD.bazel @@ -0,0 +1,85 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-io-ts-utils" +PKG_REQUIRE_NAME = "@kbn/io-ts-utils" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*" + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +SRC_DEPS = [ + "@npm//fp-ts", + "@npm//io-ts", + "@npm//lodash", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-io-ts-utils/package.json b/packages/kbn-io-ts-utils/package.json index 4d6f02d3f85a62..9d22277f27c014 100644 --- a/packages/kbn-io-ts-utils/package.json +++ b/packages/kbn-io-ts-utils/package.json @@ -4,10 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" - } + "private": true } diff --git a/packages/kbn-io-ts-utils/tsconfig.json b/packages/kbn-io-ts-utils/tsconfig.json index 6c67518e210734..7b8f2552754992 100644 --- a/packages/kbn-io-ts-utils/tsconfig.json +++ b/packages/kbn-io-ts-utils/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "./target", "stripInternal": false, "declaration": true, diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c28fd835919602..6ccf6269751b1e 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -3,7 +3,7 @@ pageLoadAssetSize: alerting: 106936 apm: 64385 apmOss: 18996 - bfetch: 41874 + bfetch: 51874 canvas: 1066647 charts: 195358 cloud: 21076 diff --git a/packages/kbn-server-route-repository/package.json b/packages/kbn-server-route-repository/package.json index ce1ca02d0c4f6b..4ae625d83a7003 100644 --- a/packages/kbn-server-route-repository/package.json +++ b/packages/kbn-server-route-repository/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/io-ts-utils": "link:../kbn-io-ts-utils" } } diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 4029ce28faf5bf..d3755ed7c5f293 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -44,6 +44,8 @@ export const Theme = require('./theme.ts'); export const Lodash = require('lodash'); export const LodashFp = require('lodash/fp'); +export const Fflate = require('fflate/esm/browser'); + // runtime deps which don't need to be copied across all bundles export const TsLib = require('tslib'); export const KbnAnalytics = require('@kbn/analytics'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 62ddb09d25add7..877bf3df6c039d 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -52,6 +52,7 @@ exports.externals = { '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', lodash: '__kbnSharedDeps__.Lodash', 'lodash/fp': '__kbnSharedDeps__.LodashFp', + fflate: '__kbnSharedDeps__.Fflate', /** * runtime deps which don't need to be copied across all bundles diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts index df74a4e1282e43..05da335d708840 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts @@ -37,7 +37,7 @@ describe('actions', () => { describe('fetchIndices', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.fetchIndices(client, ['my_index']); + const task = Actions.fetchIndices({ client, indices: ['my_index'] }); try { await task(); } catch (e) { @@ -49,7 +49,7 @@ describe('actions', () => { describe('setWriteBlock', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.setWriteBlock(client, 'my_index'); + const task = Actions.setWriteBlock({ client, index: 'my_index' }); try { await task(); } catch (e) { @@ -58,7 +58,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -66,7 +69,11 @@ describe('actions', () => { describe('cloneIndex', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.cloneIndex(client, 'my_source_index', 'my_target_index'); + const task = Actions.cloneIndex({ + client, + source: 'my_source_index', + target: 'my_target_index', + }); try { await task(); } catch (e) { @@ -75,7 +82,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -95,7 +105,7 @@ describe('actions', () => { describe('openPit', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.openPit(client, 'my_index'); + const task = Actions.openPit({ client, index: 'my_index' }); try { await task(); } catch (e) { @@ -107,7 +117,12 @@ describe('actions', () => { describe('readWithPit', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.readWithPit(client, 'pitId', { match_all: {} }, 10_000); + const task = Actions.readWithPit({ + client, + pitId: 'pitId', + query: { match_all: {} }, + batchSize: 10_000, + }); try { await task(); } catch (e) { @@ -119,7 +134,7 @@ describe('actions', () => { describe('closePit', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.closePit(client, 'pitId'); + const task = Actions.closePit({ client, pitId: 'pitId' }); try { await task(); } catch (e) { @@ -131,14 +146,14 @@ describe('actions', () => { describe('reindex', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.reindex( + const task = Actions.reindex({ client, - 'my_source_index', - 'my_target_index', - Option.none, - false, - {} - ); + sourceIndex: 'my_source_index', + targetIndex: 'my_target_index', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: {}, + }); try { await task(); } catch (e) { @@ -150,7 +165,7 @@ describe('actions', () => { describe('waitForReindexTask', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.waitForReindexTask(client, 'my task id', '60s'); + const task = Actions.waitForReindexTask({ client, taskId: 'my task id', timeout: '60s' }); try { await task(); } catch (e) { @@ -160,7 +175,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -168,7 +186,11 @@ describe('actions', () => { describe('waitForPickupUpdatedMappingsTask', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.waitForPickupUpdatedMappingsTask(client, 'my task id', '60s'); + const task = Actions.waitForPickupUpdatedMappingsTask({ + client, + taskId: 'my task id', + timeout: '60s', + }); try { await task(); } catch (e) { @@ -178,7 +200,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -186,7 +211,7 @@ describe('actions', () => { describe('updateAliases', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.updateAliases(client, []); + const task = Actions.updateAliases({ client, aliasActions: [] }); try { await task(); } catch (e) { @@ -196,7 +221,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -204,7 +232,11 @@ describe('actions', () => { describe('createIndex', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.createIndex(client, 'new_index', { properties: {} }); + const task = Actions.createIndex({ + client, + indexName: 'new_index', + mappings: { properties: {} }, + }); try { await task(); } catch (e) { @@ -214,7 +246,10 @@ describe('actions', () => { expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(retryableError); }); it('re-throws non retry-able errors', async () => { - const task = Actions.setWriteBlock(clientWithNonRetryableError, 'my_index'); + const task = Actions.setWriteBlock({ + client: clientWithNonRetryableError, + index: 'my_index', + }); await task(); expect(catchRetryableEsClientErrors).toHaveBeenCalledWith(nonRetryableError); }); @@ -222,7 +257,11 @@ describe('actions', () => { describe('updateAndPickupMappings', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.updateAndPickupMappings(client, 'new_index', { properties: {} }); + const task = Actions.updateAndPickupMappings({ + client, + index: 'new_index', + mappings: { properties: {} }, + }); try { await task(); } catch (e) { @@ -276,7 +315,12 @@ describe('actions', () => { describe('bulkOverwriteTransformedDocuments', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.bulkOverwriteTransformedDocuments(client, 'new_index', [], 'wait_for'); + const task = Actions.bulkOverwriteTransformedDocuments({ + client, + index: 'new_index', + transformedDocs: [], + refresh: 'wait_for', + }); try { await task(); } catch (e) { @@ -289,7 +333,7 @@ describe('actions', () => { describe('refreshIndex', () => { it('calls catchRetryableEsClientErrors when the promise rejects', async () => { - const task = Actions.refreshIndex(client, 'target_index'); + const task = Actions.refreshIndex({ client, targetIndex: 'target_index' }); try { await task(); } catch (e) { diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index c2e0476960c3b3..905d64947298ec 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -68,20 +68,26 @@ export type FetchIndexResponse = Record< { aliases: Record; mappings: IndexMapping; settings: unknown } >; +/** @internal */ +export interface FetchIndicesParams { + client: ElasticsearchClient; + indices: string[]; +} + /** * Fetches information about the given indices including aliases, mappings and * settings. */ -export const fetchIndices = ( - client: ElasticsearchClient, - indicesToFetch: string[] -): TaskEither.TaskEither => +export const fetchIndices = ({ + client, + indices, +}: FetchIndicesParams): TaskEither.TaskEither => // @ts-expect-error @elastic/elasticsearch IndexState.alias and IndexState.mappings should be required () => { return client.indices .get( { - index: indicesToFetch, + index: indices, ignore_unavailable: true, // Don't return an error for missing indices. Note this *will* include closed indices, the docs are misleading https://github.com/elastic/elasticsearch/issues/63607 }, { ignore: [404], maxRetries: 0 } @@ -96,6 +102,12 @@ export interface IndexNotFound { type: 'index_not_found_exception'; index: string; } + +/** @internal */ +export interface SetWriteBlockParams { + client: ElasticsearchClient; + index: string; +} /** * Sets a write block in place for the given index. If the response includes * `acknowledged: true` all in-progress writes have drained and no further @@ -105,10 +117,10 @@ export interface IndexNotFound { * include `shards_acknowledged: true` but once the block is in place, * subsequent calls return `shards_acknowledged: false` */ -export const setWriteBlock = ( - client: ElasticsearchClient, - index: string -): TaskEither.TaskEither< +export const setWriteBlock = ({ + client, + index, +}: SetWriteBlockParams): TaskEither.TaskEither< IndexNotFound | RetryableEsClientError, 'set_write_block_succeeded' > => () => { @@ -145,13 +157,21 @@ export const setWriteBlock = ( ); }; +/** @internal */ +export interface RemoveWriteBlockParams { + client: ElasticsearchClient; + index: string; +} /** * Removes a write block from an index */ -export const removeWriteBlock = ( - client: ElasticsearchClient, - index: string -): TaskEither.TaskEither => () => { +export const removeWriteBlock = ({ + client, + index, +}: RemoveWriteBlockParams): TaskEither.TaskEither< + RetryableEsClientError, + 'remove_write_block_succeeded' +> => () => { return client.indices .putSettings<{ acknowledged: boolean; @@ -182,6 +202,12 @@ export const removeWriteBlock = ( .catch(catchRetryableEsClientErrors); }; +/** @internal */ +export interface WaitForIndexStatusYellowParams { + client: ElasticsearchClient; + index: string; + timeout?: string; +} /** * A yellow index status means the index's primary shard is allocated and the * index is ready for searching/indexing documents, but ES wasn't able to @@ -193,11 +219,11 @@ export const removeWriteBlock = ( * yellow at any point in the future. So ultimately data-redundancy is up to * users to maintain. */ -export const waitForIndexStatusYellow = ( - client: ElasticsearchClient, - index: string, - timeout = DEFAULT_TIMEOUT -): TaskEither.TaskEither => () => { +export const waitForIndexStatusYellow = ({ + client, + index, + timeout = DEFAULT_TIMEOUT, +}: WaitForIndexStatusYellowParams): TaskEither.TaskEither => () => { return client.cluster .health({ index, wait_for_status: 'yellow', timeout }) .then(() => { @@ -208,6 +234,14 @@ export const waitForIndexStatusYellow = ( export type CloneIndexResponse = AcknowledgeResponse; +/** @internal */ +export interface CloneIndexParams { + client: ElasticsearchClient; + source: string; + target: string; + /** only used for testing */ + timeout?: string; +} /** * Makes a clone of the source index into the target. * @@ -218,13 +252,15 @@ export type CloneIndexResponse = AcknowledgeResponse; * - the first call will wait up to 120s for the cluster state and all shards * to be updated. */ -export const cloneIndex = ( - client: ElasticsearchClient, - source: string, - target: string, - /** only used for testing */ - timeout = DEFAULT_TIMEOUT -): TaskEither.TaskEither => { +export const cloneIndex = ({ + client, + source, + target, + timeout = DEFAULT_TIMEOUT, +}: CloneIndexParams): TaskEither.TaskEither< + RetryableEsClientError | IndexNotFound, + CloneIndexResponse +> => { const cloneTask: TaskEither.TaskEither< RetryableEsClientError | IndexNotFound, AcknowledgeResponse @@ -302,7 +338,7 @@ export const cloneIndex = ( } else { // Otherwise, wait until the target index has a 'green' status. return pipe( - waitForIndexStatusYellow(client, target, timeout), + waitForIndexStatusYellow({ client, index: target, timeout }), TaskEither.map((value) => { /** When the index status is 'green' we know that all shards were started */ return { acknowledged: true, shardsAcknowledged: true }; @@ -352,16 +388,22 @@ const catchWaitForTaskCompletionTimeout = ( } }; +/** @internal */ +export interface WaitForTaskParams { + client: ElasticsearchClient; + taskId: string; + timeout: string; +} /** * Blocks for up to 60s or until a task completes. * * TODO: delete completed tasks */ -const waitForTask = ( - client: ElasticsearchClient, - taskId: string, - timeout: string -): TaskEither.TaskEither< +const waitForTask = ({ + client, + taskId, + timeout, +}: WaitForTaskParams): TaskEither.TaskEither< RetryableEsClientError | WaitForTaskCompletionTimeout, WaitForTaskResponse > => () => { @@ -433,16 +475,21 @@ export interface OpenPitResponse { pitId: string; } +/** @internal */ +export interface OpenPitParams { + client: ElasticsearchClient; + index: string; +} // how long ES should keep PIT alive const pitKeepAlive = '10m'; /* * Creates a lightweight view of data when the request has been initiated. * See https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html * */ -export const openPit = ( - client: ElasticsearchClient, - index: string -): TaskEither.TaskEither => () => { +export const openPit = ({ + client, + index, +}: OpenPitParams): TaskEither.TaskEither => () => { return client .openPointInTime({ index, @@ -459,17 +506,28 @@ export interface ReadWithPit { readonly totalHits: number | undefined; } +/** @internal */ + +export interface ReadWithPitParams { + client: ElasticsearchClient; + pitId: string; + query: estypes.QueryContainer; + batchSize: number; + searchAfter?: number[]; + seqNoPrimaryTerm?: boolean; +} + /* * Requests documents from the index using PIT mechanism. * */ -export const readWithPit = ( - client: ElasticsearchClient, - pitId: string, - query: estypes.QueryContainer, - batchSize: number, - searchAfter?: number[], - seqNoPrimaryTerm?: boolean -): TaskEither.TaskEither => () => { +export const readWithPit = ({ + client, + pitId, + query, + batchSize, + searchAfter, + seqNoPrimaryTerm, +}: ReadWithPitParams): TaskEither.TaskEither => () => { return client .search({ seq_no_primary_term: seqNoPrimaryTerm, @@ -516,14 +574,19 @@ export const readWithPit = ( .catch(catchRetryableEsClientErrors); }; +/** @internal */ +export interface ClosePitParams { + client: ElasticsearchClient; + pitId: string; +} /* * Closes PIT. * See https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html * */ -export const closePit = ( - client: ElasticsearchClient, - pitId: string -): TaskEither.TaskEither => () => { +export const closePit = ({ + client, + pitId, +}: ClosePitParams): TaskEither.TaskEither => () => { return client .closePointInTime({ body: { id: pitId }, @@ -537,27 +600,42 @@ export const closePit = ( .catch(catchRetryableEsClientErrors); }; +/** @internal */ +export interface TransformDocsParams { + transformRawDocs: TransformRawDocs; + outdatedDocuments: SavedObjectsRawDoc[]; +} /* * Transform outdated docs * */ -export const transformDocs = ( - transformRawDocs: TransformRawDocs, - outdatedDocuments: SavedObjectsRawDoc[] -): TaskEither.TaskEither => - transformRawDocs(outdatedDocuments); +export const transformDocs = ({ + transformRawDocs, + outdatedDocuments, +}: TransformDocsParams): TaskEither.TaskEither< + DocumentsTransformFailed, + DocumentsTransformSuccess +> => transformRawDocs(outdatedDocuments); /** @internal */ export interface ReindexResponse { taskId: string; } +/** @internal */ +export interface RefreshIndexParams { + client: ElasticsearchClient; + targetIndex: string; +} /** * Wait for Elasticsearch to reindex all the changes. */ -export const refreshIndex = ( - client: ElasticsearchClient, - targetIndex: string -): TaskEither.TaskEither => () => { +export const refreshIndex = ({ + client, + targetIndex, +}: RefreshIndexParams): TaskEither.TaskEither< + RetryableEsClientError, + { refreshed: boolean } +> => () => { return client.indices .refresh({ index: targetIndex, @@ -567,6 +645,19 @@ export const refreshIndex = ( }) .catch(catchRetryableEsClientErrors); }; +/** @internal */ +export interface ReindexParams { + client: ElasticsearchClient; + sourceIndex: string; + targetIndex: string; + reindexScript: Option.Option; + requireAlias: boolean; + /* When reindexing we use a source query to exclude saved objects types which + * are no longer used. These saved objects will still be kept in the outdated + * index for backup purposes, but won't be available in the upgraded index. + */ + unusedTypesQuery: estypes.QueryContainer; +} /** * Reindex documents from the `sourceIndex` into the `targetIndex`. Returns a * task ID which can be tracked for progress. @@ -575,18 +666,14 @@ export const refreshIndex = ( * this in parallel. By using `op_type: 'create', conflicts: 'proceed'` there * will be only one write per reindexed document. */ -export const reindex = ( - client: ElasticsearchClient, - sourceIndex: string, - targetIndex: string, - reindexScript: Option.Option, - requireAlias: boolean, - /* When reindexing we use a source query to exclude saved objects types which - * are no longer used. These saved objects will still be kept in the outdated - * index for backup purposes, but won't be available in the upgraded index. - */ - unusedTypesQuery: estypes.QueryContainer -): TaskEither.TaskEither => () => { +export const reindex = ({ + client, + sourceIndex, + targetIndex, + reindexScript, + requireAlias, + unusedTypesQuery, +}: ReindexParams): TaskEither.TaskEither => () => { return client .reindex({ // Require targetIndex to be an alias. Prevents a new index from being @@ -688,11 +775,18 @@ export const waitForReindexTask = flow( ) ); -export const verifyReindex = ( - client: ElasticsearchClient, - sourceIndex: string, - targetIndex: string -): TaskEither.TaskEither< +/** @internal */ +export interface VerifyReindexParams { + client: ElasticsearchClient; + sourceIndex: string; + targetIndex: string; +} + +export const verifyReindex = ({ + client, + sourceIndex, + targetIndex, +}: VerifyReindexParams): TaskEither.TaskEither< RetryableEsClientError | { type: 'verify_reindex_failed' }, 'verify_reindex_succeeded' > => () => { @@ -762,13 +856,18 @@ export type AliasAction = | { remove: { index: string; alias: string; must_exist: boolean } } | { add: { index: string; alias: string } }; +/** @internal */ +export interface UpdateAliasesParams { + client: ElasticsearchClient; + aliasActions: AliasAction[]; +} /** * Calls the Update index alias API `_alias` with the provided alias actions. */ -export const updateAliases = ( - client: ElasticsearchClient, - aliasActions: AliasAction[] -): TaskEither.TaskEither< +export const updateAliases = ({ + client, + aliasActions, +}: UpdateAliasesParams): TaskEither.TaskEither< IndexNotFound | AliasNotFound | RemoveIndexNotAConcreteIndex | RetryableEsClientError, 'update_aliases_succeeded' > => () => { @@ -836,6 +935,14 @@ function aliasArrayToRecord(aliases: string[]): Record { } return result; } + +/** @internal */ +export interface CreateIndexParams { + client: ElasticsearchClient; + indexName: string; + mappings: IndexMapping; + aliases?: string[]; +} /** * Creates an index with the given mappings * @@ -846,12 +953,12 @@ function aliasArrayToRecord(aliases: string[]): Record { * - the first call will wait up to 120s for the cluster state and all shards * to be updated. */ -export const createIndex = ( - client: ElasticsearchClient, - indexName: string, - mappings: IndexMapping, - aliases: string[] = [] -): TaskEither.TaskEither => { +export const createIndex = ({ + client, + indexName, + mappings, + aliases = [], +}: CreateIndexParams): TaskEither.TaskEither => { const createIndexTask: TaskEither.TaskEither< RetryableEsClientError, AcknowledgeResponse @@ -930,7 +1037,7 @@ export const createIndex = ( } else { // Otherwise, wait until the target index has a 'yellow' status. return pipe( - waitForIndexStatusYellow(client, indexName, DEFAULT_TIMEOUT), + waitForIndexStatusYellow({ client, index: indexName, timeout: DEFAULT_TIMEOUT }), TaskEither.map(() => { /** When the index status is 'yellow' we know that all shards were started */ return 'create_index_succeeded'; @@ -946,15 +1053,24 @@ export interface UpdateAndPickupMappingsResponse { taskId: string; } +/** @internal */ +export interface UpdateAndPickupMappingsParams { + client: ElasticsearchClient; + index: string; + mappings: IndexMapping; +} /** * Updates an index's mappings and runs an pickupUpdatedMappings task so that the mapping * changes are "picked up". Returns a taskId to track progress. */ -export const updateAndPickupMappings = ( - client: ElasticsearchClient, - index: string, - mappings: IndexMapping -): TaskEither.TaskEither => { +export const updateAndPickupMappings = ({ + client, + index, + mappings, +}: UpdateAndPickupMappingsParams): TaskEither.TaskEither< + RetryableEsClientError, + UpdateAndPickupMappingsResponse +> => { const putMappingTask: TaskEither.TaskEither< RetryableEsClientError, 'update_mappings_succeeded' @@ -1053,16 +1169,26 @@ export const searchForOutdatedDocuments = ( .catch(catchRetryableEsClientErrors); }; +/** @internal */ +export interface BulkOverwriteTransformedDocumentsParams { + client: ElasticsearchClient; + index: string; + transformedDocs: SavedObjectsRawDoc[]; + refresh?: estypes.Refresh; +} /** * Write the up-to-date transformed documents to the index, overwriting any * documents that are still on their outdated version. */ -export const bulkOverwriteTransformedDocuments = ( - client: ElasticsearchClient, - index: string, - transformedDocs: SavedObjectsRawDoc[], - refresh: estypes.Refresh -): TaskEither.TaskEither => () => { +export const bulkOverwriteTransformedDocuments = ({ + client, + index, + transformedDocs, + refresh = false, +}: BulkOverwriteTransformedDocumentsParams): TaskEither.TaskEither< + RetryableEsClientError, + 'bulk_index_succeeded' +> => () => { return client .bulk({ // Because we only add aliases in the MARK_VERSION_INDEX_READY step we diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index d0158a4c68f246..67a2685caf3d61 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -67,9 +67,13 @@ describe('migration actions', () => { client = start.elasticsearch.client.asInternalUser; // Create test fixture data: - await createIndex(client, 'existing_index_with_docs', { - dynamic: true, - properties: {}, + await createIndex({ + client, + indexName: 'existing_index_with_docs', + mappings: { + dynamic: true, + properties: {}, + }, })(); const sourceDocs = ([ { _source: { title: 'doc 1' } }, @@ -78,25 +82,30 @@ describe('migration actions', () => { { _source: { title: 'saved object 4', type: 'another_unused_type' } }, { _source: { title: 'f-agent-event 5', type: 'f_agent_event' } }, ] as unknown) as SavedObjectsRawDoc[]; - await bulkOverwriteTransformedDocuments( + await bulkOverwriteTransformedDocuments({ + client, + index: 'existing_index_with_docs', + transformedDocs: sourceDocs, + refresh: 'wait_for', + })(); + + await createIndex({ client, indexName: 'existing_index_2', mappings: { properties: {} } })(); + await createIndex({ client, - 'existing_index_with_docs', - sourceDocs, - 'wait_for' - )(); - - await createIndex(client, 'existing_index_2', { properties: {} })(); - await createIndex(client, 'existing_index_with_write_block', { properties: {} })(); - await bulkOverwriteTransformedDocuments( + indexName: 'existing_index_with_write_block', + mappings: { properties: {} }, + })(); + await bulkOverwriteTransformedDocuments({ client, - 'existing_index_with_write_block', - sourceDocs, - 'wait_for' - )(); - await setWriteBlock(client, 'existing_index_with_write_block')(); - await updateAliases(client, [ - { add: { index: 'existing_index_2', alias: 'existing_index_2_alias' } }, - ])(); + index: 'existing_index_with_write_block', + transformedDocs: sourceDocs, + refresh: 'wait_for', + })(); + await setWriteBlock({ client, index: 'existing_index_with_write_block' })(); + await updateAliases({ + client, + aliasActions: [{ add: { index: 'existing_index_2', alias: 'existing_index_2_alias' } }], + })(); }); afterAll(async () => { @@ -107,7 +116,7 @@ describe('migration actions', () => { describe('fetchIndices', () => { it('resolves right empty record if no indices were found', async () => { expect.assertions(1); - const task = fetchIndices(client, ['no_such_index']); + const task = fetchIndices({ client, indices: ['no_such_index'] }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -117,10 +126,10 @@ describe('migration actions', () => { }); it('resolves right record with found indices', async () => { expect.assertions(1); - const res = (await fetchIndices(client, [ - 'no_such_index', - 'existing_index_with_docs', - ])()) as Either.Right; + const res = (await fetchIndices({ + client, + indices: ['no_such_index', 'existing_index_with_docs'], + })()) as Either.Right; expect(res.right).toEqual( expect.objectContaining({ @@ -136,11 +145,15 @@ describe('migration actions', () => { describe('setWriteBlock', () => { beforeAll(async () => { - await createIndex(client, 'new_index_without_write_block', { properties: {} })(); + await createIndex({ + client, + indexName: 'new_index_without_write_block', + mappings: { properties: {} }, + })(); }); it('resolves right when setting the write block succeeds', async () => { expect.assertions(1); - const task = setWriteBlock(client, 'new_index_without_write_block'); + const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -150,7 +163,7 @@ describe('migration actions', () => { }); it('resolves right when setting a write block on an index that already has one', async () => { expect.assertions(1); - const task = setWriteBlock(client, 'existing_index_with_write_block'); + const task = setWriteBlock({ client, index: 'existing_index_with_write_block' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -160,7 +173,7 @@ describe('migration actions', () => { }); it('once resolved, prevents further writes to the index', async () => { expect.assertions(1); - const task = setWriteBlock(client, 'new_index_without_write_block'); + const task = setWriteBlock({ client, index: 'new_index_without_write_block' }); await task(); const sourceDocs = ([ { _source: { title: 'doc 1' } }, @@ -169,17 +182,17 @@ describe('migration actions', () => { { _source: { title: 'doc 4' } }, ] as unknown) as SavedObjectsRawDoc[]; await expect( - bulkOverwriteTransformedDocuments( + bulkOverwriteTransformedDocuments({ client, - 'new_index_without_write_block', - sourceDocs, - 'wait_for' - )() + index: 'new_index_without_write_block', + transformedDocs: sourceDocs, + refresh: 'wait_for', + })() ).rejects.toMatchObject(expect.anything()); }); it('resolves left index_not_found_exception when the index does not exist', async () => { expect.assertions(1); - const task = setWriteBlock(client, 'no_such_index'); + const task = setWriteBlock({ client, index: 'no_such_index' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -194,13 +207,21 @@ describe('migration actions', () => { describe('removeWriteBlock', () => { beforeAll(async () => { - await createIndex(client, 'existing_index_without_write_block_2', { properties: {} })(); - await createIndex(client, 'existing_index_with_write_block_2', { properties: {} })(); - await setWriteBlock(client, 'existing_index_with_write_block_2')(); + await createIndex({ + client, + indexName: 'existing_index_without_write_block_2', + mappings: { properties: {} }, + })(); + await createIndex({ + client, + indexName: 'existing_index_with_write_block_2', + mappings: { properties: {} }, + })(); + await setWriteBlock({ client, index: 'existing_index_with_write_block_2' })(); }); it('resolves right if successful when an index already has a write block', async () => { expect.assertions(1); - const task = removeWriteBlock(client, 'existing_index_with_write_block_2'); + const task = removeWriteBlock({ client, index: 'existing_index_with_write_block_2' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -210,7 +231,7 @@ describe('migration actions', () => { }); it('resolves right if successful when an index does not have a write block', async () => { expect.assertions(1); - const task = removeWriteBlock(client, 'existing_index_without_write_block_2'); + const task = removeWriteBlock({ client, index: 'existing_index_without_write_block_2' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -220,7 +241,7 @@ describe('migration actions', () => { }); it('rejects if there is a non-retryable error', async () => { expect.assertions(1); - const task = removeWriteBlock(client, 'no_such_index'); + const task = removeWriteBlock({ client, index: 'no_such_index' }); await expect(task()).rejects.toMatchInlineSnapshot( `[ResponseError: index_not_found_exception]` ); @@ -251,7 +272,10 @@ describe('migration actions', () => { ); // Start tracking the index status - const indexStatusPromise = waitForIndexStatusYellow(client, 'red_then_yellow_index')(); + const indexStatusPromise = waitForIndexStatusYellow({ + client, + index: 'red_then_yellow_index', + })(); const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); expect(redStatusResponse.body.status).toBe('red'); @@ -281,7 +305,11 @@ describe('migration actions', () => { } }); it('resolves right if cloning into a new target index', async () => { - const task = cloneIndex(client, 'existing_index_with_write_block', 'clone_target_1'); + const task = cloneIndex({ + client, + source: 'existing_index_with_write_block', + target: 'clone_target_1', + }); expect.assertions(1); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -314,11 +342,11 @@ describe('migration actions', () => { .catch((e) => {}); // Call clone even though the index already exists - const cloneIndexPromise = cloneIndex( + const cloneIndexPromise = cloneIndex({ client, - 'existing_index_with_write_block', - 'clone_red_then_yellow_index' - )(); + source: 'existing_index_with_write_block', + target: 'clone_red_then_yellow_index', + })(); let indexYellow = false; setTimeout(() => { @@ -348,7 +376,7 @@ describe('migration actions', () => { }); it('resolves left index_not_found_exception if the source index does not exist', async () => { expect.assertions(1); - const task = cloneIndex(client, 'no_such_index', 'clone_target_3'); + const task = cloneIndex({ client, source: 'no_such_index', target: 'clone_target_3' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -378,12 +406,12 @@ describe('migration actions', () => { .catch((e) => {}); // Call clone even though the index already exists - const cloneIndexPromise = cloneIndex( + const cloneIndexPromise = cloneIndex({ client, - 'existing_index_with_write_block', - 'clone_red_index', - '0s' - )(); + source: 'existing_index_with_write_block', + target: 'clone_red_index', + timeout: '0s', + })(); await cloneIndexPromise.then((res) => { expect(res).toMatchInlineSnapshot(` @@ -404,15 +432,15 @@ describe('migration actions', () => { // together with waitForReindexTask describe('reindex & waitForReindexTask', () => { it('resolves right when reindex succeeds without reindex script', async () => { - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -436,21 +464,21 @@ describe('migration actions', () => { `); }); it('resolves right and excludes all documents not matching the unusedTypesQuery', async () => { - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_excluded_docs', - Option.none, - false, - { + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_excluded_docs', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { bool: { must_not: ['f_agent_event', 'another_unused_type'].map((type) => ({ term: { type }, })), }, - } - )()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -473,15 +501,15 @@ describe('migration actions', () => { }); it('resolves right when reindex succeeds with reindex script', async () => { expect.assertions(2); - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_2', - Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false, - { match_all: {} } - )()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_2', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -506,15 +534,15 @@ describe('migration actions', () => { it('resolves right, ignores version conflicts and does not update existing docs when reindex multiple times', async () => { expect.assertions(3); // Reindex with a script - let res = (await reindex( + let res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_3', - Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false, - { match_all: {} } - )()) as Either.Right; - let task = waitForReindexTask(client, res.right.taskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_3', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + let task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -523,15 +551,15 @@ describe('migration actions', () => { `); // reindex without a script - res = (await reindex( + res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_3', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; - task = waitForReindexTask(client, res.right.taskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_3', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -559,7 +587,7 @@ describe('migration actions', () => { expect.assertions(2); // Simulate a reindex that only adds some of the documents from the // source index into the target index - await createIndex(client, 'reindex_target_4', { properties: {} })(); + await createIndex({ client, indexName: 'reindex_target_4', mappings: { properties: {} } })(); const sourceDocs = ((await searchForOutdatedDocuments(client, { batchSize: 1000, targetIndex: 'existing_index_with_docs', @@ -570,18 +598,23 @@ describe('migration actions', () => { _id, _source, })); - await bulkOverwriteTransformedDocuments(client, 'reindex_target_4', sourceDocs, 'wait_for')(); + await bulkOverwriteTransformedDocuments({ + client, + index: 'reindex_target_4', + transformedDocs: sourceDocs, + refresh: 'wait_for', + })(); // Now do a real reindex - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_4', - Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false, - { match_all: {} } - )()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_4', + reindexScript: Option.some(`ctx._source.title = ctx._source.title + '_updated'`), + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -614,24 +647,28 @@ describe('migration actions', () => { // and should ignore this error. // Create an index with incompatible mappings - await createIndex(client, 'reindex_target_5', { - dynamic: 'strict', - properties: { - /** no title field */ + await createIndex({ + client, + indexName: 'reindex_target_5', + mappings: { + dynamic: 'strict', + properties: { + /** no title field */ + }, }, })(); const { right: { taskId: reindexTaskId }, - } = (await reindex( + } = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_5', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; - const task = waitForReindexTask(client, reindexTaskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_5', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -651,22 +688,26 @@ describe('migration actions', () => { // and should ignore this error. // Create an index with incompatible mappings - await createIndex(client, 'reindex_target_6', { - dynamic: false, - properties: { title: { type: 'integer' } }, // integer is incompatible with string title + await createIndex({ + client, + indexName: 'reindex_target_6', + mappings: { + dynamic: false, + properties: { title: { type: 'integer' } }, // integer is incompatible with string title + }, })(); const { right: { taskId: reindexTaskId }, - } = (await reindex( + } = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_6', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; - const task = waitForReindexTask(client, reindexTaskId, '10s'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_6', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + const task = waitForReindexTask({ client, taskId: reindexTaskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -679,10 +720,17 @@ describe('migration actions', () => { }); it('resolves left index_not_found_exception if source index does not exist', async () => { expect.assertions(1); - const res = (await reindex(client, 'no_such_index', 'reindex_target', Option.none, false, { - match_all: {}, + const res = (await reindex({ + client, + sourceIndex: 'no_such_index', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { + match_all: {}, + }, })()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -695,16 +743,16 @@ describe('migration actions', () => { }); it('resolves left target_index_had_write_block if all failures are due to a write block', async () => { expect.assertions(1); - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'existing_index_with_write_block', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; + sourceIndex: 'existing_index_with_docs', + targetIndex: 'existing_index_with_write_block', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -717,16 +765,16 @@ describe('migration actions', () => { }); it('resolves left if requireAlias=true and the target is not an alias', async () => { expect.assertions(1); - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'existing_index_with_write_block', - Option.none, - true, - { match_all: {} } - )()) as Either.Right; + sourceIndex: 'existing_index_with_docs', + targetIndex: 'existing_index_with_write_block', + reindexScript: Option.none, + requireAlias: true, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '10s'); + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -739,16 +787,16 @@ describe('migration actions', () => { `); }); it('resolves left wait_for_task_completion_timeout when the task does not finish within the timeout', async () => { - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; - const task = waitForReindexTask(client, res.right.taskId, '0s'); + const task = waitForReindexTask({ client, taskId: res.right.taskId, timeout: '0s' }); await expect(task()).resolves.toMatchObject({ _tag: 'Left', @@ -766,17 +814,21 @@ describe('migration actions', () => { describe('verifyReindex', () => { it('resolves right if source and target indices have the same amount of documents', async () => { expect.assertions(1); - const res = (await reindex( + const res = (await reindex({ client, - 'existing_index_with_docs', - 'reindex_target_7', - Option.none, - false, - { match_all: {} } - )()) as Either.Right; - await waitForReindexTask(client, res.right.taskId, '10s')(); - - const task = verifyReindex(client, 'existing_index_with_docs', 'reindex_target_7'); + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_7', + reindexScript: Option.none, + requireAlias: false, + unusedTypesQuery: { match_all: {} }, + })()) as Either.Right; + await waitForReindexTask({ client, taskId: res.right.taskId, timeout: '10s' })(); + + const task = verifyReindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'reindex_target_7', + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -786,7 +838,11 @@ describe('migration actions', () => { }); it('resolves left if source and target indices have different amount of documents', async () => { expect.assertions(1); - const task = verifyReindex(client, 'existing_index_with_docs', 'existing_index_2'); + const task = verifyReindex({ + client, + sourceIndex: 'existing_index_with_docs', + targetIndex: 'existing_index_2', + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -798,19 +854,27 @@ describe('migration actions', () => { }); it('rejects if source or target index does not exist', async () => { expect.assertions(2); - let task = verifyReindex(client, 'no_such_index', 'existing_index_2'); + let task = verifyReindex({ + client, + sourceIndex: 'no_such_index', + targetIndex: 'existing_index_2', + }); await expect(task()).rejects.toMatchInlineSnapshot( `[ResponseError: index_not_found_exception]` ); - task = verifyReindex(client, 'existing_index_2', 'no_such_index'); + task = verifyReindex({ + client, + sourceIndex: 'existing_index_2', + targetIndex: 'no_such_index', + }); await expect(task()).rejects.toThrow('index_not_found_exception'); }); }); describe('openPit', () => { it('opens PointInTime for an index', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; expect(pitResponse.right.pitId).toEqual(expect.any(String)); @@ -824,52 +888,52 @@ describe('migration actions', () => { await expect(searchResponse.body.hits.hits.length).toBeGreaterThan(0); }); it('rejects if index does not exist', async () => { - const openPitTask = openPit(client, 'no_such_index'); + const openPitTask = openPit({ client, index: 'no_such_index' }); await expect(openPitTask()).rejects.toThrow('index_not_found_exception'); }); }); describe('readWithPit', () => { it('requests documents from an index using given PIT', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { match_all: {} }, - 1000, - undefined - ); + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 1000, + searchAfter: undefined, + }); const docsResponse = (await readWithPitTask()) as Either.Right; await expect(docsResponse.right.outdatedDocuments.length).toBe(5); }); it('requests the batchSize of documents from an index', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { match_all: {} }, - 3, - undefined - ); + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 3, + searchAfter: undefined, + }); const docsResponse = (await readWithPitTask()) as Either.Right; await expect(docsResponse.right.outdatedDocuments.length).toBe(3); }); it('it excludes documents not matching the provided "query"', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { + pitId: pitResponse.right.pitId, + query: { bool: { must_not: [ { @@ -885,9 +949,9 @@ describe('migration actions', () => { ], }, }, - 1000, - undefined - ); + batchSize: 1000, + searchAfter: undefined, + }); const docsResponse = (await readWithPitTask()) as Either.Right; @@ -902,18 +966,18 @@ describe('migration actions', () => { }); it('only returns documents that match the provided "query"', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { + pitId: pitResponse.right.pitId, + query: { match: { title: { query: 'doc' } }, }, - 1000, - undefined - ); + batchSize: 1000, + searchAfter: undefined, + }); const docsResponse = (await readWithPitTask()) as Either.Right; @@ -928,19 +992,19 @@ describe('migration actions', () => { }); it('returns docs with _seq_no and _primary_term when specified', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { + pitId: pitResponse.right.pitId, + query: { match: { title: { query: 'doc' } }, }, - 1000, - undefined, - true - ); + batchSize: 1000, + searchAfter: undefined, + seqNoPrimaryTerm: true, + }); const docsResponse = (await readWithPitTask()) as Either.Right; @@ -955,18 +1019,18 @@ describe('migration actions', () => { }); it('does not return docs with _seq_no and _primary_term if not specified', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - pitResponse.right.pitId, - { + pitId: pitResponse.right.pitId, + query: { match: { title: { query: 'doc' } }, }, - 1000, - undefined - ); + batchSize: 1000, + searchAfter: undefined, + }); const docsResponse = (await readWithPitTask()) as Either.Right; @@ -981,24 +1045,24 @@ describe('migration actions', () => { }); it('rejects if PIT does not exist', async () => { - const readWithPitTask = readWithPit( + const readWithPitTask = readWithPit({ client, - 'no_such_pit', - { match_all: {} }, - 1000, - undefined - ); + pitId: 'no_such_pit', + query: { match_all: {} }, + batchSize: 1000, + searchAfter: undefined, + }); await expect(readWithPitTask()).rejects.toThrow('illegal_argument_exception'); }); }); describe('closePit', () => { it('closes PointInTime', async () => { - const openPitTask = openPit(client, 'existing_index_with_docs'); + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); const pitResponse = (await openPitTask()) as Either.Right; const pitId = pitResponse.right.pitId; - await closePit(client, pitId)(); + await closePit({ client, pitId })(); const searchTask = client.search({ body: { @@ -1010,7 +1074,7 @@ describe('migration actions', () => { }); it('rejects if PIT does not exist', async () => { - const closePitTask = closePit(client, 'no_such_pit'); + const closePitTask = closePit({ client, pitId: 'no_such_pit' }); await expect(closePitTask()).rejects.toThrow('illegal_argument_exception'); }); }); @@ -1034,7 +1098,10 @@ describe('migration actions', () => { return Either.right({ processedDocs }); }; } - const transformTask = transformDocs(innerTransformRawDocs, originalDocs); + const transformTask = transformDocs({ + transformRawDocs: innerTransformRawDocs, + outdatedDocuments: originalDocs, + }); const resultsWithProcessDocs = ((await transformTask()) as Either.Right) .right.processedDocs; @@ -1051,7 +1118,11 @@ describe('migration actions', () => { 'existing_index_with_write_block' )()) as Either.Right; - const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '10s'); + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); // We can't do a snapshot match because the response includes an index // id which ES assigns dynamically @@ -1065,7 +1136,11 @@ describe('migration actions', () => { 'no_such_index' )()) as Either.Right; - const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '10s'); + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); await expect(task()).rejects.toMatchInlineSnapshot(` [Error: pickupUpdatedMappings task failed with the following error: @@ -1078,7 +1153,11 @@ describe('migration actions', () => { 'existing_index_with_docs' )()) as Either.Right; - const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '0s'); + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '0s', + }); await expect(task()).resolves.toMatchObject({ _tag: 'Left', @@ -1097,7 +1176,11 @@ describe('migration actions', () => { 'existing_index_with_docs' )()) as Either.Right; - const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '10s'); + const task = waitForPickupUpdatedMappingsTask({ + client, + taskId: res.right.taskId, + timeout: '10s', + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -1111,9 +1194,13 @@ describe('migration actions', () => { describe('updateAndPickupMappings', () => { it('resolves right when mappings were updated and picked up', async () => { // Create an index without any mappings and insert documents into it - await createIndex(client, 'existing_index_without_mappings', { - dynamic: false, - properties: {}, + await createIndex({ + client, + indexName: 'existing_index_without_mappings', + mappings: { + dynamic: false, + properties: {}, + }, })(); const sourceDocs = ([ { _source: { title: 'doc 1' } }, @@ -1121,12 +1208,12 @@ describe('migration actions', () => { { _source: { title: 'doc 3' } }, { _source: { title: 'doc 4' } }, ] as unknown) as SavedObjectsRawDoc[]; - await bulkOverwriteTransformedDocuments( + await bulkOverwriteTransformedDocuments({ client, - 'existing_index_without_mappings', - sourceDocs, - 'wait_for' - )(); + index: 'existing_index_without_mappings', + transformedDocs: sourceDocs, + refresh: 'wait_for', + })(); // Assert that we can't search over the unmapped fields of the document const originalSearchResults = ((await searchForOutdatedDocuments(client, { @@ -1139,14 +1226,18 @@ describe('migration actions', () => { expect(originalSearchResults.length).toBe(0); // Update and pickup mappings so that the title field is searchable - const res = await updateAndPickupMappings(client, 'existing_index_without_mappings', { - properties: { - title: { type: 'text' }, + const res = await updateAndPickupMappings({ + client, + index: 'existing_index_without_mappings', + mappings: { + properties: { + title: { type: 'text' }, + }, }, })(); expect(Either.isRight(res)).toBe(true); const taskId = (res as Either.Right).right.taskId; - await waitForPickupUpdatedMappingsTask(client, taskId, '60s')(); + await waitForPickupUpdatedMappingsTask({ client, taskId, timeout: '60s' })(); // Repeat the search expecting to be able to find the existing documents const pickedUpSearchResults = ((await searchForOutdatedDocuments(client, { @@ -1163,15 +1254,18 @@ describe('migration actions', () => { describe('updateAliases', () => { describe('remove', () => { it('resolves left index_not_found_exception when the index does not exist', async () => { - const task = updateAliases(client, [ - { - remove: { - alias: 'no_such_alias', - index: 'no_such_index', - must_exist: false, + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'no_such_index', + must_exist: false, + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1184,15 +1278,18 @@ describe('migration actions', () => { }); describe('with must_exist=false', () => { it('resolves left alias_not_found_exception when alias does not exist', async () => { - const task = updateAliases(client, [ - { - remove: { - alias: 'no_such_alias', - index: 'existing_index_with_docs', - must_exist: false, + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'existing_index_with_docs', + must_exist: false, + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1205,15 +1302,18 @@ describe('migration actions', () => { }); describe('with must_exist=true', () => { it('resolves left alias_not_found_exception when alias does not exist on specified index', async () => { - const task = updateAliases(client, [ - { - remove: { - alias: 'existing_index_2_alias', - index: 'existing_index_with_docs', - must_exist: true, + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'existing_index_2_alias', + index: 'existing_index_with_docs', + must_exist: true, + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1224,15 +1324,18 @@ describe('migration actions', () => { `); }); it('resolves left alias_not_found_exception when alias does not exist', async () => { - const task = updateAliases(client, [ - { - remove: { - alias: 'no_such_alias', - index: 'existing_index_with_docs', - must_exist: true, + const task = updateAliases({ + client, + aliasActions: [ + { + remove: { + alias: 'no_such_alias', + index: 'existing_index_with_docs', + must_exist: true, + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1246,13 +1349,16 @@ describe('migration actions', () => { }); describe('remove_index', () => { it('left index_not_found_exception if index does not exist', async () => { - const task = updateAliases(client, [ - { - remove_index: { - index: 'no_such_index', + const task = updateAliases({ + client, + aliasActions: [ + { + remove_index: { + index: 'no_such_index', + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1264,13 +1370,16 @@ describe('migration actions', () => { `); }); it('left remove_index_not_a_concrete_index when remove_index targets an alias', async () => { - const task = updateAliases(client, [ - { - remove_index: { - index: 'existing_index_2_alias', + const task = updateAliases({ + client, + aliasActions: [ + { + remove_index: { + index: 'existing_index_2_alias', + }, }, - }, - ]); + ], + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -1312,7 +1421,11 @@ describe('migration actions', () => { }); // Call createIndex even though the index already exists - const createIndexPromise = createIndex(client, 'red_then_yellow_index', undefined as any)(); + const createIndexPromise = createIndex({ + client, + indexName: 'red_then_yellow_index', + mappings: undefined as any, + })(); let indexYellow = false; setTimeout(() => { @@ -1341,7 +1454,7 @@ describe('migration actions', () => { // Creating an index with the same name as an existing alias to induce // failure await expect( - createIndex(client, 'existing_index_2_alias', undefined as any)() + createIndex({ client, indexName: 'existing_index_2_alias', mappings: undefined as any })() ).rejects.toMatchInlineSnapshot(`[ResponseError: invalid_index_name_exception]`); }); }); @@ -1353,12 +1466,12 @@ describe('migration actions', () => { { _source: { title: 'doc 6' } }, { _source: { title: 'doc 7' } }, ] as unknown) as SavedObjectsRawDoc[]; - const task = bulkOverwriteTransformedDocuments( + const task = bulkOverwriteTransformedDocuments({ client, - 'existing_index_with_docs', - newDocs, - 'wait_for' - ); + index: 'existing_index_with_docs', + transformedDocs: newDocs, + refresh: 'wait_for', + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { @@ -1374,12 +1487,15 @@ describe('migration actions', () => { outdatedDocumentsQuery: undefined, })()) as Either.Right).right.outdatedDocuments; - const task = bulkOverwriteTransformedDocuments( + const task = bulkOverwriteTransformedDocuments({ client, - 'existing_index_with_docs', - [...existingDocs, ({ _source: { title: 'doc 8' } } as unknown) as SavedObjectsRawDoc], - 'wait_for' - ); + index: 'existing_index_with_docs', + transformedDocs: [ + ...existingDocs, + ({ _source: { title: 'doc 8' } } as unknown) as SavedObjectsRawDoc, + ], + refresh: 'wait_for', + }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -1394,12 +1510,12 @@ describe('migration actions', () => { { _source: { title: 'doc 7' } }, ] as unknown) as SavedObjectsRawDoc[]; await expect( - bulkOverwriteTransformedDocuments( + bulkOverwriteTransformedDocuments({ client, - 'existing_index_with_write_block', - newDocs, - 'wait_for' - )() + index: 'existing_index_with_write_block', + transformedDocs: newDocs, + refresh: 'wait_for', + })() ).rejects.toMatchObject(expect.anything()); }); }); diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_machine_cleanup.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_machine_cleanup.ts index 1881f9a712c293..e9cb33c0aa54a2 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_machine_cleanup.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_machine_cleanup.ts @@ -19,7 +19,7 @@ export async function cleanup( if (!state) return; if ('sourceIndexPitId' in state) { try { - await Actions.closePit(client, state.sourceIndexPitId)(); + await Actions.closePit({ client, pitId: state.sourceIndexPitId })(); } catch (e) { executionLog.push({ type: 'cleanup', diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 07ebf80271d48c..3c3e3c46a8d68f 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -58,38 +58,46 @@ export type ResponseType = UnwrapPromise< export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: TransformRawDocs) => { return { INIT: (state: InitState) => - Actions.fetchIndices(client, [state.currentAlias, state.versionAlias]), + Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => - Actions.waitForIndexStatusYellow(client, state.sourceIndex.value), + Actions.waitForIndexStatusYellow({ client, index: state.sourceIndex.value }), SET_SOURCE_WRITE_BLOCK: (state: SetSourceWriteBlockState) => - Actions.setWriteBlock(client, state.sourceIndex.value), + Actions.setWriteBlock({ client, index: state.sourceIndex.value }), CREATE_NEW_TARGET: (state: CreateNewTargetState) => - Actions.createIndex(client, state.targetIndex, state.targetIndexMappings), + Actions.createIndex({ + client, + indexName: state.targetIndex, + mappings: state.targetIndexMappings, + }), CREATE_REINDEX_TEMP: (state: CreateReindexTempState) => - Actions.createIndex(client, state.tempIndex, state.tempIndexMappings), + Actions.createIndex({ + client, + indexName: state.tempIndex, + mappings: state.tempIndexMappings, + }), REINDEX_SOURCE_TO_TEMP_OPEN_PIT: (state: ReindexSourceToTempOpenPit) => - Actions.openPit(client, state.sourceIndex.value), + Actions.openPit({ client, index: state.sourceIndex.value }), REINDEX_SOURCE_TO_TEMP_READ: (state: ReindexSourceToTempRead) => - Actions.readWithPit( + Actions.readWithPit({ client, - state.sourceIndexPitId, + pitId: state.sourceIndexPitId, /* When reading we use a source query to exclude saved objects types which * are no longer used. These saved objects will still be kept in the outdated * index for backup purposes, but won't be available in the upgraded index. */ - state.unusedTypesQuery, - state.batchSize, - state.lastHitSortValue - ), + query: state.unusedTypesQuery, + batchSize: state.batchSize, + searchAfter: state.lastHitSortValue, + }), REINDEX_SOURCE_TO_TEMP_CLOSE_PIT: (state: ReindexSourceToTempClosePit) => - Actions.closePit(client, state.sourceIndexPitId), + Actions.closePit({ client, pitId: state.sourceIndexPitId }), REINDEX_SOURCE_TO_TEMP_INDEX: (state: ReindexSourceToTempIndex) => - Actions.transformDocs(transformRawDocs, state.outdatedDocuments), + Actions.transformDocs({ transformRawDocs, outdatedDocuments: state.outdatedDocuments }), REINDEX_SOURCE_TO_TEMP_INDEX_BULK: (state: ReindexSourceToTempIndexBulk) => - Actions.bulkOverwriteTransformedDocuments( + Actions.bulkOverwriteTransformedDocuments({ client, - state.tempIndex, - state.transformedDocs, + index: state.tempIndex, + transformedDocs: state.transformedDocs, /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. @@ -97,39 +105,48 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra * before we reach out to the OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT step. * Right now, it's performed during REFRESH_TARGET step. */ - false - ), + refresh: false, + }), SET_TEMP_WRITE_BLOCK: (state: SetTempWriteBlock) => - Actions.setWriteBlock(client, state.tempIndex), + Actions.setWriteBlock({ client, index: state.tempIndex }), CLONE_TEMP_TO_TARGET: (state: CloneTempToSource) => - Actions.cloneIndex(client, state.tempIndex, state.targetIndex), - REFRESH_TARGET: (state: RefreshTarget) => Actions.refreshIndex(client, state.targetIndex), + Actions.cloneIndex({ client, source: state.tempIndex, target: state.targetIndex }), + REFRESH_TARGET: (state: RefreshTarget) => + Actions.refreshIndex({ client, targetIndex: state.targetIndex }), UPDATE_TARGET_MAPPINGS: (state: UpdateTargetMappingsState) => - Actions.updateAndPickupMappings(client, state.targetIndex, state.targetIndexMappings), + Actions.updateAndPickupMappings({ + client, + index: state.targetIndex, + mappings: state.targetIndexMappings, + }), UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK: (state: UpdateTargetMappingsWaitForTaskState) => - Actions.waitForPickupUpdatedMappingsTask(client, state.updateTargetMappingsTaskId, '60s'), + Actions.waitForPickupUpdatedMappingsTask({ + client, + taskId: state.updateTargetMappingsTaskId, + timeout: '60s', + }), OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT: (state: OutdatedDocumentsSearchOpenPit) => - Actions.openPit(client, state.targetIndex), + Actions.openPit({ client, index: state.targetIndex }), OUTDATED_DOCUMENTS_SEARCH_READ: (state: OutdatedDocumentsSearchRead) => - Actions.readWithPit( + Actions.readWithPit({ client, - state.pitId, + pitId: state.pitId, // search for outdated documents only - state.outdatedDocumentsQuery, - state.batchSize, - state.lastHitSortValue - ), + query: state.outdatedDocumentsQuery, + batchSize: state.batchSize, + searchAfter: state.lastHitSortValue, + }), OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT: (state: OutdatedDocumentsSearchClosePit) => - Actions.closePit(client, state.pitId), + Actions.closePit({ client, pitId: state.pitId }), OUTDATED_DOCUMENTS_REFRESH: (state: OutdatedDocumentsRefresh) => - Actions.refreshIndex(client, state.targetIndex), + Actions.refreshIndex({ client, targetIndex: state.targetIndex }), OUTDATED_DOCUMENTS_TRANSFORM: (state: OutdatedDocumentsTransform) => - Actions.transformDocs(transformRawDocs, state.outdatedDocuments), + Actions.transformDocs({ transformRawDocs, outdatedDocuments: state.outdatedDocuments }), TRANSFORMED_DOCUMENTS_BULK_INDEX: (state: TransformedDocumentsBulkIndex) => - Actions.bulkOverwriteTransformedDocuments( + Actions.bulkOverwriteTransformedDocuments({ client, - state.targetIndex, - state.transformedDocs, + index: state.targetIndex, + transformedDocs: state.transformedDocs, /** * Since we don't run a search against the target index, we disable "refresh" to speed up * the migration process. @@ -137,29 +154,32 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra * before we reach out to the MARK_VERSION_INDEX_READY step. * Right now, it's performed during OUTDATED_DOCUMENTS_REFRESH step. */ - false - ), + }), MARK_VERSION_INDEX_READY: (state: MarkVersionIndexReady) => - Actions.updateAliases(client, state.versionIndexReadyActions.value), + Actions.updateAliases({ client, aliasActions: state.versionIndexReadyActions.value }), MARK_VERSION_INDEX_READY_CONFLICT: (state: MarkVersionIndexReadyConflict) => - Actions.fetchIndices(client, [state.currentAlias, state.versionAlias]), + Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), LEGACY_SET_WRITE_BLOCK: (state: LegacySetWriteBlockState) => - Actions.setWriteBlock(client, state.legacyIndex), + Actions.setWriteBlock({ client, index: state.legacyIndex }), LEGACY_CREATE_REINDEX_TARGET: (state: LegacyCreateReindexTargetState) => - Actions.createIndex(client, state.sourceIndex.value, state.legacyReindexTargetMappings), + Actions.createIndex({ + client, + indexName: state.sourceIndex.value, + mappings: state.legacyReindexTargetMappings, + }), LEGACY_REINDEX: (state: LegacyReindexState) => - Actions.reindex( + Actions.reindex({ client, - state.legacyIndex, - state.sourceIndex.value, - state.preMigrationScript, - false, - state.unusedTypesQuery - ), + sourceIndex: state.legacyIndex, + targetIndex: state.sourceIndex.value, + reindexScript: state.preMigrationScript, + requireAlias: false, + unusedTypesQuery: state.unusedTypesQuery, + }), LEGACY_REINDEX_WAIT_FOR_TASK: (state: LegacyReindexWaitForTaskState) => - Actions.waitForReindexTask(client, state.legacyReindexTaskId, '60s'), + Actions.waitForReindexTask({ client, taskId: state.legacyReindexTaskId, timeout: '60s' }), LEGACY_DELETE: (state: LegacyDeleteState) => - Actions.updateAliases(client, state.legacyPreMigrationDoneActions), + Actions.updateAliases({ client, aliasActions: state.legacyPreMigrationDoneActions }), }; }; diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts index 186962b568792f..cf27505e8f073c 100644 --- a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts @@ -7,6 +7,7 @@ */ import { schema as s, ObjectType } from '@kbn/config-schema'; +import { sortOrderSchema } from './common_schemas'; /** * Schemas for the Bucket aggregations. @@ -85,6 +86,12 @@ export const bucketAggsSchemas: Record = { min_doc_count: s.maybe(s.number({ min: 1 })), size: s.maybe(s.number()), show_term_doc_count_error: s.maybe(s.boolean()), - order: s.maybe(s.oneOf([s.literal('asc'), s.literal('desc')])), + order: s.maybe( + s.oneOf([ + sortOrderSchema, + s.recordOf(s.string(), sortOrderSchema), + s.arrayOf(s.recordOf(s.string(), sortOrderSchema)), + ]) + ), }), }; diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/common_schemas.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/common_schemas.ts new file mode 100644 index 00000000000000..92a3096374687d --- /dev/null +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/common_schemas.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema as s } from '@kbn/config-schema'; + +// note: these schemas are not exhaustive. See the `Sort` type of `@elastic/elasticsearch` if you need to enhance it. +const fieldSchema = s.string(); +export const sortOrderSchema = s.oneOf([s.literal('asc'), s.literal('desc'), s.literal('_doc')]); +const sortModeSchema = s.oneOf([ + s.literal('min'), + s.literal('max'), + s.literal('sum'), + s.literal('avg'), + s.literal('median'), +]); +const fieldSortSchema = s.object({ + missing: s.maybe(s.oneOf([s.string(), s.number(), s.boolean()])), + mode: s.maybe(sortModeSchema), + order: s.maybe(sortOrderSchema), + // nested and unmapped_type not implemented yet +}); +const sortContainerSchema = s.recordOf(s.string(), s.oneOf([sortOrderSchema, fieldSortSchema])); +const sortCombinationsSchema = s.oneOf([fieldSchema, sortContainerSchema]); +export const sortSchema = s.oneOf([sortCombinationsSchema, s.arrayOf(sortCombinationsSchema)]); diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/metrics_aggs.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/metrics_aggs.ts index c05ae67cd2164e..fb7e25fae19f29 100644 --- a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/metrics_aggs.ts +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/metrics_aggs.ts @@ -7,6 +7,7 @@ */ import { schema as s, ObjectType } from '@kbn/config-schema'; +import { sortSchema } from './common_schemas'; /** * Schemas for the metrics Aggregations @@ -68,7 +69,7 @@ export const metricsAggsSchemas: Record = { stored_fields: s.maybe(s.oneOf([s.string(), s.arrayOf(s.string())])), from: s.maybe(s.number()), size: s.maybe(s.number()), - sort: s.maybe(s.oneOf([s.literal('asc'), s.literal('desc')])), + sort: s.maybe(sortSchema), seq_no_primary_term: s.maybe(s.boolean()), version: s.maybe(s.boolean()), track_scores: s.maybe(s.boolean()), diff --git a/src/plugins/apm_oss/public/index.ts b/src/plugins/apm_oss/public/index.ts index d5fcabbe146a96..fea8ac4a8a1e42 100644 --- a/src/plugins/apm_oss/public/index.ts +++ b/src/plugins/apm_oss/public/index.ts @@ -14,5 +14,3 @@ export function plugin() { return new ApmOssPlugin(); } export { ApmOssPluginSetup, ApmOssPluginStart } from './types'; - -export { APM_STATIC_INDEX_PATTERN_ID } from '../common/index_pattern_constants'; diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index a02e28201a1b90..1424cb1c7126fb 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -7,7 +7,6 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import apmIndexPattern from './tutorial/index_pattern.json'; import { PluginInitializerContext } from '../../../core/server'; import { APMOSSPlugin } from './plugin'; @@ -32,20 +31,3 @@ export function plugin(initializerContext: PluginInitializerContext) { export type APMOSSConfig = TypeOf; export { APMOSSPluginSetup } from './plugin'; - -export { apmIndexPattern }; - -export { APM_STATIC_INDEX_PATTERN_ID } from '../common/index_pattern_constants'; - -export { - createNodeAgentInstructions, - createDjangoAgentInstructions, - createFlaskAgentInstructions, - createRailsAgentInstructions, - createRackAgentInstructions, - createJsAgentInstructions, - createGoAgentInstructions, - createJavaAgentInstructions, - createDotNetAgentInstructions, - createPhpAgentInstructions, -} from './tutorial/instructions/apm_agent_instructions'; diff --git a/src/plugins/apm_oss/server/plugin.ts b/src/plugins/apm_oss/server/plugin.ts index e504d5f0b9a9fa..02a8ac38be2a39 100644 --- a/src/plugins/apm_oss/server/plugin.ts +++ b/src/plugins/apm_oss/server/plugin.ts @@ -6,38 +6,18 @@ * Side Public License, v 1. */ -import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Observable } from 'rxjs'; +import { Plugin, PluginInitializerContext } from 'src/core/server'; import { APMOSSConfig } from './'; -import { HomeServerPluginSetup, TutorialProvider } from '../../home/server'; -import { tutorialProvider } from './tutorial'; export class APMOSSPlugin implements Plugin { constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; } - public setup(core: CoreSetup, plugins: { home: HomeServerPluginSetup }) { + public setup() { const config$ = this.initContext.config.create(); - const config = this.initContext.config.get(); - - const apmTutorialProvider = tutorialProvider({ - indexPatternTitle: config.indexPattern, - indices: { - errorIndices: config.errorIndices, - metricsIndices: config.metricsIndices, - onboardingIndices: config.onboardingIndices, - sourcemapIndices: config.sourcemapIndices, - transactionIndices: config.transactionIndices, - }, - }); - plugins.home.tutorials.registerTutorial(apmTutorialProvider); - - return { - config, - config$, - getRegisteredTutorialProvider: () => apmTutorialProvider, - }; + return { config, config$ }; } start() {} @@ -47,5 +27,4 @@ export class APMOSSPlugin implements Plugin { export interface APMOSSPluginSetup { config: APMOSSConfig; config$: Observable; - getRegisteredTutorialProvider(): TutorialProvider; } diff --git a/src/plugins/apm_oss/server/tutorial/index.ts b/src/plugins/apm_oss/server/tutorial/index.ts deleted file mode 100644 index ce7fec406e7ac0..00000000000000 --- a/src/plugins/apm_oss/server/tutorial/index.ts +++ /dev/null @@ -1,84 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { onPremInstructions } from './envs/on_prem'; -import apmIndexPattern from './index_pattern.json'; -import { ArtifactsSchema, TutorialsCategory } from '../../../../../src/plugins/home/server'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../common/index_pattern_constants'; - -const apmIntro = i18n.translate('apmOss.tutorial.introduction', { - defaultMessage: 'Collect in-depth performance metrics and errors from inside your applications.', -}); -const moduleName = 'apm'; - -export const tutorialProvider = ({ - indexPatternTitle, - indices, -}: { - indexPatternTitle: string; - indices: { - errorIndices: string; - transactionIndices: string; - metricsIndices: string; - sourcemapIndices: string; - onboardingIndices: string; - }; -}) => () => { - const savedObjects = [ - { - ...apmIndexPattern, - id: APM_STATIC_INDEX_PATTERN_ID, - attributes: { - ...apmIndexPattern.attributes, - title: indexPatternTitle, - }, - }, - ]; - - const artifacts: ArtifactsSchema = { - dashboards: [ - { - id: '8d3ed660-7828-11e7-8c47-65b845b5cfb3', - linkLabel: i18n.translate('apmOss.tutorial.specProvider.artifacts.dashboards.linkLabel', { - defaultMessage: 'APM dashboard', - }), - isOverview: true, - }, - ], - }; - - return { - id: 'apm', - name: i18n.translate('apmOss.tutorial.specProvider.name', { - defaultMessage: 'APM', - }), - moduleName, - category: TutorialsCategory.OTHER, - shortDescription: apmIntro, - longDescription: i18n.translate('apmOss.tutorial.specProvider.longDescription', { - defaultMessage: - 'Application Performance Monitoring (APM) collects in-depth \ -performance metrics and errors from inside your application. \ -It allows you to monitor the performance of thousands of applications in real time. \ -[Learn more]({learnMoreLink}).', - values: { - learnMoreLink: - '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', - }, - }), - euiIconType: 'apmApp', - artifacts, - onPrem: onPremInstructions(indices), - previewImagePath: '/plugins/apmOss/assets/apm.png', - savedObjects, - savedObjectsInstallMsg: i18n.translate('apmOss.tutorial.specProvider.savedObjectsInstallMsg', { - defaultMessage: 'An APM index pattern is required for some features in the APM UI.', - }), - }; -}; diff --git a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts deleted file mode 100644 index ba2b062870cf68..00000000000000 --- a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts +++ /dev/null @@ -1,754 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -export const createNodeAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.nodeClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.nodeClient.install.textPre', { - defaultMessage: 'Install the APM agent for Node.js as a dependency to your application.', - }), - commands: ['npm install elastic-apm-node --save'], - }, - { - title: i18n.translate('apmOss.tutorial.nodeClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.nodeClient.configure.textPre', { - defaultMessage: - 'Agents are libraries that run inside of your application process. \ -APM services are created programmatically based on the `serviceName`. \ -This agent supports a variety of frameworks but can also be used with your custom stack.', - }), - commands: `// ${i18n.translate( - 'apmOss.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', - { - defaultMessage: 'Add this to the VERY top of the first file loaded in your app', - } - )} -var apm = require('elastic-apm-node').start({curlyOpen} - - // ${i18n.translate( - 'apmOss.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Override the service name from package.json', - } - )} - // ${i18n.translate('apmOss.tutorial.nodeClient.configure.commands.allowedCharactersComment', { - defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', - })} - serviceName: '', - - // ${i18n.translate( - 'apmOss.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - secretToken: '${secretToken}', - - // ${i18n.translate( - 'apmOss.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - serverUrl: '${apmServerUrl}', - - // ${i18n.translate( - 'apmOss.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - environment: 'production' -{curlyClose})`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.nodeClient.configure.textPost', { - defaultMessage: - 'See [the documentation]({documentationLink}) for advanced usage, including how to use with \ -[Babel/ES Modules]({babelEsModulesLink}).', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/nodejs/current/index.html', - babelEsModulesLink: - '{config.docs.base_url}guide/en/apm/agent/nodejs/current/advanced-setup.html#es-modules', - }, - }), - }, -]; - -export const createDjangoAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.djangoClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.djangoClient.install.textPre', { - defaultMessage: 'Install the APM agent for Python as a dependency.', - }), - commands: ['$ pip install elastic-apm'], - }, - { - title: i18n.translate('apmOss.tutorial.djangoClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.djangoClient.configure.textPre', { - defaultMessage: - 'Agents are libraries that run inside of your application process. \ -APM services are created programmatically based on the `SERVICE_NAME`.', - }), - commands: `# ${i18n.translate( - 'apmOss.tutorial.djangoClient.configure.commands.addAgentComment', - { - defaultMessage: 'Add the agent to the installed apps', - } - )} -INSTALLED_APPS = ( - 'elasticapm.contrib.django', - # ... -) - -ELASTIC_APM = {curlyOpen} - # ${i18n.translate( - 'apmOss.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } - )} - # ${i18n.translate('apmOss.tutorial.djangoClient.configure.commands.allowedCharactersComment', { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - })} - 'SERVICE_NAME': '', - - # ${i18n.translate( - 'apmOss.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - 'SECRET_TOKEN': '${secretToken}', - - # ${i18n.translate( - 'apmOss.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - 'SERVER_URL': '${apmServerUrl}', - - # ${i18n.translate( - 'apmOss.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - 'ENVIRONMENT': 'production', -{curlyClose} - -# ${i18n.translate('apmOss.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment', { - defaultMessage: 'To send performance metrics, add our tracing middleware:', - })} -MIDDLEWARE = ( - 'elasticapm.contrib.django.middleware.TracingMiddleware', - #... -)`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.djangoClient.configure.textPost', { - defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/agent/python/current/django-support.html', - }, - }), - }, -]; - -export const createFlaskAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.flaskClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.flaskClient.install.textPre', { - defaultMessage: 'Install the APM agent for Python as a dependency.', - }), - commands: ['$ pip install elastic-apm[flask]'], - }, - { - title: i18n.translate('apmOss.tutorial.flaskClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.flaskClient.configure.textPre', { - defaultMessage: - 'Agents are libraries that run inside of your application process. \ -APM services are created programmatically based on the `SERVICE_NAME`.', - }), - commands: `# ${i18n.translate( - 'apmOss.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', - { - defaultMessage: 'initialize using environment variables', - } - )} -from elasticapm.contrib.flask import ElasticAPM -app = Flask(__name__) -apm = ElasticAPM(app) - -# ${i18n.translate('apmOss.tutorial.flaskClient.configure.commands.configureElasticApmComment', { - defaultMessage: "or configure to use ELASTIC_APM in your application's settings", - })} -from elasticapm.contrib.flask import ElasticAPM -app.config['ELASTIC_APM'] = {curlyOpen} - # ${i18n.translate( - 'apmOss.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } - )} - # ${i18n.translate('apmOss.tutorial.flaskClient.configure.commands.allowedCharactersComment', { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - })} - 'SERVICE_NAME': '', - - # ${i18n.translate( - 'apmOss.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - 'SECRET_TOKEN': '${secretToken}', - - # ${i18n.translate( - 'apmOss.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - 'SERVER_URL': '${apmServerUrl}', - - # ${i18n.translate( - 'apmOss.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - 'ENVIRONMENT': 'production', -{curlyClose} - -apm = ElasticAPM(app)`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.flaskClient.configure.textPost', { - defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/agent/python/current/flask-support.html', - }, - }), - }, -]; - -export const createRailsAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.railsClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.railsClient.install.textPre', { - defaultMessage: 'Add the agent to your Gemfile.', - }), - commands: [`gem 'elastic-apm'`], - }, - { - title: i18n.translate('apmOss.tutorial.railsClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.railsClient.configure.textPre', { - defaultMessage: - 'APM is automatically started when your app boots. Configure the agent, by creating the config file {configFile}', - values: { configFile: '`config/elastic_apm.yml`' }, - }), - commands: `# config/elastic_apm.yml: - -# Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space -# Defaults to the name of your Rails app -service_name: 'my-service' - -# Use if APM Server requires a secret token -secret_token: '${secretToken}' - -# Set the custom APM Server URL (default: http://localhost:8200) -server_url: '${apmServerUrl || 'http://localhost:8200'}' - -# Set the service environment -environment: 'production'`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.railsClient.configure.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/ruby/current/index.html', - }, - }), - }, -]; - -export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.rackClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.rackClient.install.textPre', { - defaultMessage: 'Add the agent to your Gemfile.', - }), - commands: [`gem 'elastic-apm'`], - }, - { - title: i18n.translate('apmOss.tutorial.rackClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.rackClient.configure.textPre', { - defaultMessage: - 'For Rack or a compatible framework (e.g. Sinatra), include the middleware in your app and start the agent.', - }), - commands: `# config.ru - require 'sinatra/base' - - class MySinatraApp < Sinatra::Base - use ElasticAPM::Middleware - - # ... - end - - ElasticAPM.start( - app: MySinatraApp, # ${i18n.translate( - 'apmOss.tutorial.rackClient.configure.commands.requiredComment', - { - defaultMessage: 'required', - } - )} - config_file: '' # ${i18n.translate( - 'apmOss.tutorial.rackClient.configure.commands.optionalComment', - { - defaultMessage: 'optional, defaults to config/elastic_apm.yml', - } - )} - ) - - run MySinatraApp - - at_exit {curlyOpen} ElasticAPM.stop {curlyClose}`.split('\n'), - }, - { - title: i18n.translate('apmOss.tutorial.rackClient.createConfig.title', { - defaultMessage: 'Create config file', - }), - textPre: i18n.translate('apmOss.tutorial.rackClient.createConfig.textPre', { - defaultMessage: 'Create a config file {configFile}:', - values: { configFile: '`config/elastic_apm.yml`' }, - }), - commands: `# config/elastic_apm.yml: - -# ${i18n.translate('apmOss.tutorial.rackClient.createConfig.commands.setServiceNameComment', { - defaultMessage: 'Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', - })} -# ${i18n.translate( - 'apmOss.tutorial.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', - { - defaultMessage: "Defaults to the name of your Rack app's class.", - } - )} -service_name: 'my-service' - -# ${i18n.translate( - 'apmOss.tutorial.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a token', - } - )} -secret_token: '${secretToken}' - -# ${i18n.translate('apmOss.tutorial.rackClient.createConfig.commands.setCustomApmServerComment', { - defaultMessage: 'Set custom APM Server URL (default: {defaultServerUrl})', - values: { defaultServerUrl: 'http://localhost:8200' }, - })} -server_url: '${apmServerUrl || 'http://localhost:8200'}', - -# ${i18n.translate('apmOss.tutorial.rackClient.createConfig.commands.setServiceEnvironment', { - defaultMessage: 'Set the service environment', - })} -environment: 'production'`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.rackClient.createConfig.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/ruby/current/index.html', - }, - }), - }, -]; - -export const createJsAgentInstructions = (apmServerUrl = '') => [ - { - title: i18n.translate('apmOss.tutorial.jsClient.enableRealUserMonitoring.title', { - defaultMessage: 'Enable Real User Monitoring support in APM Server', - }), - textPre: i18n.translate('apmOss.tutorial.jsClient.enableRealUserMonitoring.textPre', { - defaultMessage: - 'APM Server disables RUM support by default. See the [documentation]({documentationLink}) \ -for details on how to enable RUM support.', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/server/{config.docs.version}/configuration-rum.html', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.jsClient.installDependency.title', { - defaultMessage: 'Set up the Agent as a dependency', - }), - textPre: i18n.translate('apmOss.tutorial.jsClient.installDependency.textPre', { - defaultMessage: - 'You can install the Agent as a dependency to your application with \ -`npm install @elastic/apm-rum --save`.\n\n\ -The Agent can then be initialized and configured in your application like this:', - }), - commands: `import {curlyOpen} init as initApm {curlyClose} from '@elastic/apm-rum' -var apm = initApm({curlyOpen} - - // ${i18n.translate( - 'apmOss.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment', - { - defaultMessage: - 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)', - } - )} - serviceName: 'your-app-name', - - // ${i18n.translate( - 'apmOss.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment', - { - defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - serverUrl: '${apmServerUrl}', - - // ${i18n.translate( - 'apmOss.tutorial.jsClient.installDependency.commands.setServiceVersionComment', - { - defaultMessage: 'Set the service version (required for source map feature)', - } - )} - serviceVersion: '', - - // ${i18n.translate( - 'apmOss.tutorial.jsClient.installDependency.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - environment: 'production' -{curlyClose})`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.jsClient.installDependency.textPost', { - defaultMessage: - 'Framework integrations, like React or Angular, have custom dependencies. \ -See the [integration documentation]({docLink}) for more information.', - values: { - docLink: - '{config.docs.base_url}guide/en/apm/agent/rum-js/current/framework-integrations.html', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.jsClient.scriptTags.title', { - defaultMessage: 'Set up the Agent with Script Tags', - }), - textPre: i18n.translate('apmOss.tutorial.jsClient.scriptTags.textPre', { - defaultMessage: - "Alternatively, you can use Script tags to set up and configure the Agent. \ -Add a ` - -`.split('\n'), - }, -]; - -export const createGoAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.goClient.install.title', { - defaultMessage: 'Install the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.goClient.install.textPre', { - defaultMessage: 'Install the APM agent packages for Go.', - }), - commands: ['go get go.elastic.co/apm'], - }, - { - title: i18n.translate('apmOss.tutorial.goClient.configure.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.goClient.configure.textPre', { - defaultMessage: - 'Agents are libraries that run inside of your application process. \ -APM services are created programmatically based on the executable \ -file name, or the `ELASTIC_APM_SERVICE_NAME` environment variable.', - }), - commands: `# ${i18n.translate( - 'apmOss.tutorial.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', - { - defaultMessage: 'Initialize using environment variables:', - } - )} - -# ${i18n.translate('apmOss.tutorial.goClient.configure.commands.setServiceNameComment', { - defaultMessage: 'Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.', - })} -# ${i18n.translate('apmOss.tutorial.goClient.configure.commands.usedExecutableNameComment', { - defaultMessage: - 'If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.', - })} -export ELASTIC_APM_SERVICE_NAME= - -# ${i18n.translate('apmOss.tutorial.goClient.configure.commands.setCustomApmServerUrlComment', { - defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - })} -export ELASTIC_APM_SERVER_URL=${apmServerUrl} - -# ${i18n.translate('apmOss.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment', { - defaultMessage: 'Use if APM Server requires a secret token', - })} -export ELASTIC_APM_SECRET_TOKEN=${secretToken} - -# ${i18n.translate('apmOss.tutorial.goClient.configure.commands.setServiceEnvironment', { - defaultMessage: 'Set the service environment', - })} -export ELASTIC_APM_ENVIRONMENT= -`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.goClient.configure.textPost', { - defaultMessage: 'See the [documentation]({documentationLink}) for advanced configuration.', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/go/current/configuration.html', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.goClient.instrument.title', { - defaultMessage: 'Instrument your application', - }), - textPre: i18n.translate('apmOss.tutorial.goClient.instrument.textPre', { - defaultMessage: - 'Instrument your Go application by using one of the provided instrumentation modules or \ -by using the tracer API directly.', - }), - commands: `\ -import ( - "net/http" - - "go.elastic.co/apm/module/apmhttp" -) - -func main() {curlyOpen} - mux := http.NewServeMux() - ... - http.ListenAndServe(":8080", apmhttp.Wrap(mux)) -{curlyClose} -`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.goClient.instrument.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for a detailed \ -guide to instrumenting Go source code.', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/agent/go/current/instrumenting-source.html', - }, - }), - }, -]; - -export const createJavaAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.javaClient.download.title', { - defaultMessage: 'Download the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.javaClient.download.textPre', { - defaultMessage: - 'Download the agent jar from [Maven Central]({mavenCentralLink}). \ -Do **not** add the agent as a dependency to your application.', - values: { - mavenCentralLink: 'http://search.maven.org/#search%7Cga%7C1%7Ca%3Aelastic-apm-agent', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.javaClient.startApplication.title', { - defaultMessage: 'Start your application with the javaagent flag', - }), - textPre: i18n.translate('apmOss.tutorial.javaClient.startApplication.textPre', { - defaultMessage: - 'Add the `-javaagent` flag and configure the agent with system properties.\n\n \ -* Set the required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)\n \ -* Set the custom APM Server URL (default: {customApmServerUrl})\n \ -* Set the APM Server secret token\n \ -* Set the service environment\n \ -* Set the base package of your application', - values: { customApmServerUrl: 'http://localhost:8200' }, - }), - commands: `java -javaagent:/path/to/elastic-apm-agent-.jar \\ - -Delastic.apm.service_name=my-application \\ - -Delastic.apm.server_urls=${apmServerUrl || 'http://localhost:8200'} \\ - -Delastic.apm.secret_token=${secretToken} \\ - -Delastic.apm.environment=production \\ - -Delastic.apm.application_packages=org.example \\ - -jar my-application.jar`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.javaClient.startApplication.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for configuration options and advanced \ -usage.', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/java/current/index.html', - }, - }), - }, -]; - -export const createDotNetAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.dotNetClient.download.title', { - defaultMessage: 'Download the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.dotNetClient.download.textPre', { - defaultMessage: - 'Add the the agent package(s) from [NuGet]({allNuGetPackagesLink}) to your .NET application. There are multiple \ - NuGet packages available for different use cases. \n\nFor an ASP.NET Core application with Entity Framework \ - Core download the [Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}) package. This package will automatically add every \ - agent component to your application. \n\n In case you would like to minimize the dependencies, you can use the \ - [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) package for just \ - ASP.NET Core monitoring or the [Elastic.Apm.EfCore]({efCorePackageLink}) package for just Entity Framework Core monitoring. \n\n \ - In case you only want to use the public Agent API for manual instrumentation use the [Elastic.Apm]({elasticApmPackageLink}) package.', - values: { - allNuGetPackagesLink: 'https://www.nuget.org/packages?q=Elastic.apm', - netCoreAllApmPackageLink: 'https://www.nuget.org/packages/Elastic.Apm.NetCoreAll', - aspNetCorePackageLink: 'https://www.nuget.org/packages/Elastic.Apm.AspNetCore', - efCorePackageLink: 'https://www.nuget.org/packages/Elastic.Apm.EntityFrameworkCore', - elasticApmPackageLink: 'https://www.nuget.org/packages/Elastic.Apm', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.dotNetClient.configureApplication.title', { - defaultMessage: 'Add the agent to the application', - }), - textPre: i18n.translate('apmOss.tutorial.dotNetClient.configureApplication.textPre', { - defaultMessage: - 'In case of ASP.NET Core with the `Elastic.Apm.NetCoreAll` package, call the `UseAllElasticApm` \ - method in the `Configure` method within the `Startup.cs` file.', - }), - commands: `public class Startup -{curlyOpen} - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - {curlyOpen} - app.UseAllElasticApm(Configuration); - //…rest of the method - {curlyClose} - //…rest of the class -{curlyClose}`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.dotNetClient.configureApplication.textPost', { - defaultMessage: - 'Passing an `IConfiguration` instance is optional and by doing so, the agent will read config settings through this \ - `IConfiguration` instance (e.g. from the `appsettings.json` file).', - }), - }, - { - title: i18n.translate('apmOss.tutorial.dotNetClient.configureAgent.title', { - defaultMessage: 'Sample appsettings.json file:', - }), - commands: `{curlyOpen} - "ElasticApm": {curlyOpen} - "SecretToken": "${secretToken}", - "ServerUrls": "${ - apmServerUrl || 'http://localhost:8200' - }", //Set custom APM Server URL (default: http://localhost:8200) - "ServiceName": "MyApp", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application - "Environment": "production", // Set the service environment - {curlyClose} -{curlyClose}`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.dotNetClient.configureAgent.textPost', { - defaultMessage: - 'In case you don’t pass an `IConfiguration` instance to the agent (e.g. in case of non ASP.NET Core applications) \ - you can also configure the agent through environment variables. \n \ - See [the documentation]({documentationLink}) for advanced usage.', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/agent/dotnet/current/configuration.html', - }, - }), - }, -]; - -export const createPhpAgentInstructions = (apmServerUrl = '', secretToken = '') => [ - { - title: i18n.translate('apmOss.tutorial.phpClient.download.title', { - defaultMessage: 'Download the APM agent', - }), - textPre: i18n.translate('apmOss.tutorial.phpClient.download.textPre', { - defaultMessage: - 'Download the package corresponding to your platform from [GitHub releases]({githubReleasesLink}).', - values: { - githubReleasesLink: 'https://github.com/elastic/apm-agent-php/releases', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.phpClient.installPackage.title', { - defaultMessage: 'Install the downloaded package', - }), - textPre: i18n.translate('apmOss.tutorial.phpClient.installPackage.textPre', { - defaultMessage: 'For example on Alpine Linux using APK package:', - }), - commands: ['apk add --allow-untrusted .apk'], - textPost: i18n.translate('apmOss.tutorial.phpClient.installPackage.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for installation commands on other supported platforms and advanced installation.', - values: { - documentationLink: '{config.docs.base_url}guide/en/apm/agent/php/current/setup.html', - }, - }), - }, - { - title: i18n.translate('apmOss.tutorial.phpClient.configureAgent.title', { - defaultMessage: 'Configure the agent', - }), - textPre: i18n.translate('apmOss.tutorial.phpClient.configureAgent.textPre', { - defaultMessage: - 'APM is automatically started when your app boots. Configure the agent either via `php.ini` file:', - }), - commands: `elastic_apm.server_url=http://localhost:8200 -elastic_apm.service_name="My service" -`.split('\n'), - textPost: i18n.translate('apmOss.tutorial.phpClient.configure.textPost', { - defaultMessage: - 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', - values: { - documentationLink: - '{config.docs.base_url}guide/en/apm/agent/php/current/configuration.html', - }, - }), - }, -]; diff --git a/src/plugins/bfetch/common/batch.ts b/src/plugins/bfetch/common/batch.ts index a84d94b541ae52..59b012751c66db 100644 --- a/src/plugins/bfetch/common/batch.ts +++ b/src/plugins/bfetch/common/batch.ts @@ -19,3 +19,8 @@ export interface BatchResponseItem new Promise((resolve) => setImmediate(resolve)); const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => Promise.race<'resolved' | 'rejected' | 'pending'>([ @@ -52,6 +54,7 @@ describe('createStreamingBatchedFunction()', () => { const fn = createStreamingBatchedFunction({ url: '/test', fetchStreaming, + compressionDisabled$: rxof(true), }); expect(typeof fn).toBe('function'); }); @@ -61,6 +64,7 @@ describe('createStreamingBatchedFunction()', () => { const fn = createStreamingBatchedFunction({ url: '/test', fetchStreaming, + compressionDisabled$: rxof(true), }); const res = fn({}); expect(typeof res.then).toBe('function'); @@ -74,6 +78,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); expect(fetchStreaming).toHaveBeenCalledTimes(0); @@ -93,6 +98,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); expect(fetchStreaming).toHaveBeenCalledTimes(0); @@ -107,6 +113,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); fn({ foo: 'bar' }); @@ -125,6 +132,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); fn({ foo: 'bar' }); @@ -146,14 +154,18 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); expect(fetchStreaming).toHaveBeenCalledTimes(0); fn({ foo: 'bar' }); + await flushPromises(); expect(fetchStreaming).toHaveBeenCalledTimes(0); fn({ baz: 'quix' }); + await flushPromises(); expect(fetchStreaming).toHaveBeenCalledTimes(0); fn({ full: 'yep' }); + await flushPromises(); expect(fetchStreaming).toHaveBeenCalledTimes(1); }); @@ -164,6 +176,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const abortController = new AbortController(); @@ -186,11 +199,13 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); fn({ a: '1' }); fn({ b: '2' }); fn({ c: '3' }); + await flushPromises(); expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ url: '/test', @@ -209,13 +224,16 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); fn({ a: '1' }); fn({ b: '2' }); fn({ c: '3' }); + await flushPromises(); expect(fetchStreaming).toHaveBeenCalledTimes(1); fn({ d: '4' }); + await flushPromises(); await new Promise((r) => setTimeout(r, 6)); expect(fetchStreaming).toHaveBeenCalledTimes(2); }); @@ -229,6 +247,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = fn({ a: '1' }); @@ -246,8 +265,11 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); + await flushPromises(); + const promise1 = fn({ a: '1' }); const promise2 = fn({ b: '2' }); const promise3 = fn({ c: '3' }); @@ -287,6 +309,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = fn({ a: '1' }); @@ -314,6 +337,20 @@ describe('createStreamingBatchedFunction()', () => { expect(await promise3).toEqual({ foo: 'bar 2' }); }); + test('compression is false by default', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + flushOnMaxItems: 1, + fetchStreaming, + }); + + fn({ a: '1' }); + + const dontCompress = await fetchStreaming.mock.calls[0][0].compressionDisabled$.toPromise(); + expect(dontCompress).toBe(false); + }); + test('resolves falsy results', async () => { const { fetchStreaming, stream } = setup(); const fn = createStreamingBatchedFunction({ @@ -321,6 +358,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = fn({ a: '1' }); @@ -362,6 +400,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise = fn({ a: '1' }); @@ -390,6 +429,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = of(fn({ a: '1' })); @@ -442,6 +482,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const abortController = new AbortController(); @@ -471,6 +512,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const abortController = new AbortController(); @@ -509,6 +551,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = of(fn({ a: '1' })); @@ -539,6 +582,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = of(fn({ a: '1' })); @@ -576,6 +620,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = of(fn({ a: '1' })); @@ -608,6 +653,7 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); const promise1 = of(fn({ a: '1' })); @@ -644,7 +690,9 @@ describe('createStreamingBatchedFunction()', () => { fetchStreaming, maxItemAge: 5, flushOnMaxItems: 3, + compressionDisabled$: rxof(true), }); + await flushPromises(); const promise1 = of(fn({ a: '1' })); const promise2 = of(fn({ a: '2' })); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index 2d81331f10a88e..d5f955f517d135 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ +import { Observable, of } from 'rxjs'; import { AbortError, abortSignalToPromise, defer } from '../../../kibana_utils/public'; import { ItemBufferParams, TimedItemBufferParams, createBatchedFunction, - BatchResponseItem, ErrorLike, + normalizeError, } from '../../common'; -import { fetchStreaming, split } from '../streaming'; -import { normalizeError } from '../../common'; +import { fetchStreaming } from '../streaming'; import { BatchedFunc, BatchItem } from './types'; export interface BatchedFunctionProtocolError extends ErrorLike { @@ -47,6 +47,11 @@ export interface StreamingBatchedFunctionParams { * before sending the batch request. */ maxItemAge?: TimedItemBufferParams['maxItemAge']; + + /** + * Disabled zlib compression of response chunks. + */ + compressionDisabled$?: Observable; } /** @@ -64,6 +69,7 @@ export const createStreamingBatchedFunction = ( fetchStreaming: fetchStreamingInjected = fetchStreaming, flushOnMaxItems = 25, maxItemAge = 10, + compressionDisabled$ = of(false), } = params; const [fn] = createBatchedFunction({ onCall: (payload: Payload, signal?: AbortSignal) => { @@ -119,6 +125,7 @@ export const createStreamingBatchedFunction = ( body: JSON.stringify({ batch }), method: 'POST', signal: abortController.signal, + compressionDisabled$, }); const handleStreamError = (error: any) => { @@ -127,10 +134,10 @@ export const createStreamingBatchedFunction = ( for (const { future } of items) future.reject(normalizedError); }; - stream.pipe(split('\n')).subscribe({ + stream.subscribe({ next: (json: string) => { try { - const response = JSON.parse(json) as BatchResponseItem; + const response = JSON.parse(json); if (response.error) { items[response.id].future.reject(response.error); } else if (response.result !== undefined) { diff --git a/src/plugins/bfetch/public/batching/index.ts b/src/plugins/bfetch/public/batching/index.ts new file mode 100644 index 00000000000000..115fd84cbe979f --- /dev/null +++ b/src/plugins/bfetch/public/batching/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + createStreamingBatchedFunction, + StreamingBatchedFunctionParams, +} from './create_streaming_batched_function'; diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts index ed97d468eec0b1..f97a91a0e70d37 100644 --- a/src/plugins/bfetch/public/plugin.ts +++ b/src/plugins/bfetch/public/plugin.ts @@ -7,12 +7,11 @@ */ import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; +import { from, Observable, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { fetchStreaming as fetchStreamingStatic, FetchStreamingParams } from './streaming'; -import { removeLeadingSlash } from '../common'; -import { - createStreamingBatchedFunction, - StreamingBatchedFunctionParams, -} from './batching/create_streaming_batched_function'; +import { DISABLE_BFETCH_COMPRESSION, removeLeadingSlash } from '../common'; +import { createStreamingBatchedFunction, StreamingBatchedFunctionParams } from './batching'; import { BatchedFunc } from './batching/types'; // eslint-disable-next-line @@ -43,12 +42,23 @@ export class BfetchPublicPlugin constructor(private readonly initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: BfetchPublicSetupDependencies): BfetchPublicSetup { + public setup( + core: CoreSetup, + plugins: BfetchPublicSetupDependencies + ): BfetchPublicSetup { const { version } = this.initializerContext.env.packageInfo; const basePath = core.http.basePath.get(); - const fetchStreaming = this.fetchStreaming(version, basePath); - const batchedFunction = this.batchedFunction(fetchStreaming); + const compressionDisabled$ = from(core.getStartServices()).pipe( + switchMap((deps) => { + return of(deps[0]); + }), + switchMap((coreStart) => { + return coreStart.uiSettings.get$(DISABLE_BFETCH_COMPRESSION); + }) + ); + const fetchStreaming = this.fetchStreaming(version, basePath, compressionDisabled$); + const batchedFunction = this.batchedFunction(fetchStreaming, compressionDisabled$); this.contract = { fetchStreaming, @@ -66,7 +76,8 @@ export class BfetchPublicPlugin private fetchStreaming = ( version: string, - basePath: string + basePath: string, + compressionDisabled$: Observable ): BfetchPublicSetup['fetchStreaming'] => (params) => fetchStreamingStatic({ ...params, @@ -76,13 +87,16 @@ export class BfetchPublicPlugin 'kbn-version': version, ...(params.headers || {}), }, + compressionDisabled$, }); private batchedFunction = ( - fetchStreaming: BfetchPublicContract['fetchStreaming'] + fetchStreaming: BfetchPublicContract['fetchStreaming'], + compressionDisabled$: Observable ): BfetchPublicContract['batchedFunction'] => (params) => createStreamingBatchedFunction({ ...params, + compressionDisabled$, fetchStreaming: params.fetchStreaming || fetchStreaming, }); } diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts index e804b3ea942275..a5d066f6d9a24d 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -8,6 +8,15 @@ import { fetchStreaming } from './fetch_streaming'; import { mockXMLHttpRequest } from '../test_helpers/xhr'; +import { of } from 'rxjs'; +import { promisify } from 'util'; +import { deflate } from 'zlib'; +const pDeflate = promisify(deflate); + +const compressResponse = async (resp: any) => { + const gzipped = await pDeflate(JSON.stringify(resp)); + return gzipped.toString('base64'); +}; const tick = () => new Promise((resolve) => setTimeout(resolve, 1)); @@ -21,6 +30,7 @@ test('returns XHR request', () => { setup(); const { xhr } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); expect(typeof xhr.readyState).toBe('number'); }); @@ -29,6 +39,7 @@ test('returns stream', () => { setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); expect(typeof stream.subscribe).toBe('function'); }); @@ -37,6 +48,7 @@ test('promise resolves when request completes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); let resolved = false; @@ -65,10 +77,90 @@ test('promise resolves when request completes', async () => { expect(resolved).toBe(true); }); -test('streams incoming text as it comes through', async () => { +test('promise resolves when compressed request completes', async () => { + const env = setup(); + const { stream } = fetchStreaming({ + url: 'http://example.com', + compressionDisabled$: of(false), + }); + + let resolved = false; + let result; + stream.toPromise().then((r) => { + resolved = true; + result = r; + }); + + await tick(); + expect(resolved).toBe(false); + + const msg = { foo: 'bar' }; + + // Whole message in a response + (env.xhr as any).responseText = `${await compressResponse(msg)}\n`; + env.xhr.onprogress!({} as any); + + await tick(); + expect(resolved).toBe(false); + + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 200; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(resolved).toBe(true); + expect(result).toStrictEqual(JSON.stringify(msg)); +}); + +test('promise resolves when compressed chunked request completes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(false), + }); + + let resolved = false; + let result; + stream.toPromise().then((r) => { + resolved = true; + result = r; + }); + + await tick(); + expect(resolved).toBe(false); + + const msg = { veg: 'tomato' }; + const msgToCut = await compressResponse(msg); + const part1 = msgToCut.substr(0, 3); + + // Message and a half in a response + (env.xhr as any).responseText = part1; + env.xhr.onprogress!({} as any); + + await tick(); + expect(resolved).toBe(false); + + // Half a message in a response + (env.xhr as any).responseText = `${msgToCut}\n`; + env.xhr.onprogress!({} as any); + + await tick(); + expect(resolved).toBe(false); + + (env.xhr as any).readyState = 4; + (env.xhr as any).status = 200; + env.xhr.onreadystatechange!({} as any); + + await tick(); + expect(resolved).toBe(true); + expect(result).toStrictEqual(JSON.stringify(msg)); +}); + +test('streams incoming text as it comes through, according to separators', async () => { + const env = setup(); + const { stream } = fetchStreaming({ + url: 'http://example.com', + compressionDisabled$: of(true), }); const spy = jest.fn(); @@ -80,16 +172,22 @@ test('streams incoming text as it comes through', async () => { (env.xhr as any).responseText = 'foo'; env.xhr.onprogress!({} as any); + await tick(); + expect(spy).toHaveBeenCalledTimes(0); + + (env.xhr as any).responseText = 'foo\nbar'; + env.xhr.onprogress!({} as any); + await tick(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith('foo'); - (env.xhr as any).responseText = 'foo\nbar'; + (env.xhr as any).responseText = 'foo\nbar\n'; env.xhr.onprogress!({} as any); await tick(); expect(spy).toHaveBeenCalledTimes(2); - expect(spy).toHaveBeenCalledWith('\nbar'); + expect(spy).toHaveBeenCalledWith('bar'); (env.xhr as any).readyState = 4; (env.xhr as any).status = 200; @@ -103,6 +201,7 @@ test('completes stream observable when request finishes', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); const spy = jest.fn(); @@ -127,6 +226,7 @@ test('completes stream observable when aborted', async () => { const { stream } = fetchStreaming({ url: 'http://example.com', signal: abort.signal, + compressionDisabled$: of(true), }); const spy = jest.fn(); @@ -152,6 +252,7 @@ test('promise throws when request errors', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); const spy = jest.fn(); @@ -178,6 +279,7 @@ test('stream observable errors when request errors', async () => { const env = setup(); const { stream } = fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); const spy = jest.fn(); @@ -210,6 +312,7 @@ test('sets custom headers', async () => { 'Content-Type': 'text/plain', Authorization: 'Bearer 123', }, + compressionDisabled$: of(true), }); expect(env.xhr.setRequestHeader).toHaveBeenCalledWith('Content-Type', 'text/plain'); @@ -223,6 +326,7 @@ test('uses credentials', async () => { fetchStreaming({ url: 'http://example.com', + compressionDisabled$: of(true), }); expect(env.xhr.withCredentials).toBe(true); @@ -238,6 +342,7 @@ test('opens XHR request and sends specified body', async () => { url: 'http://elastic.co', method: 'GET', body: 'foobar', + compressionDisabled$: of(true), }); expect(env.xhr.open).toHaveBeenCalledTimes(1); @@ -250,6 +355,7 @@ test('uses POST request method by default', async () => { const env = setup(); fetchStreaming({ url: 'http://elastic.co', + compressionDisabled$: of(true), }); expect(env.xhr.open).toHaveBeenCalledWith('POST', 'http://elastic.co'); }); diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.ts index d68e4d01b44f5f..1af35ef68fb85c 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ +import { Observable, of } from 'rxjs'; +import { map, share, switchMap } from 'rxjs/operators'; +import { inflateResponse } from '.'; import { fromStreamingXhr } from './from_streaming_xhr'; +import { split } from './split'; export interface FetchStreamingParams { url: string; @@ -14,6 +18,7 @@ export interface FetchStreamingParams { method?: 'GET' | 'POST'; body?: string; signal?: AbortSignal; + compressionDisabled$?: Observable; } /** @@ -26,23 +31,49 @@ export function fetchStreaming({ method = 'POST', body = '', signal, + compressionDisabled$ = of(false), }: FetchStreamingParams) { const xhr = new window.XMLHttpRequest(); - // Begin the request - xhr.open(method, url); - xhr.withCredentials = true; + const msgStream = compressionDisabled$.pipe( + switchMap((compressionDisabled) => { + // Begin the request + xhr.open(method, url); + xhr.withCredentials = true; - // Set the HTTP headers - Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); + if (!compressionDisabled) { + headers['X-Chunk-Encoding'] = 'deflate'; + } - const stream = fromStreamingXhr(xhr, signal); + // Set the HTTP headers + Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); - // Send the payload to the server - xhr.send(body); + const stream = fromStreamingXhr(xhr, signal); + + // Send the payload to the server + xhr.send(body); + + // Return a stream of chunked decompressed messages + return stream.pipe( + split('\n'), + map((msg) => { + return compressionDisabled ? msg : inflateResponse(msg); + }) + ); + }), + share() + ); + + // start execution + const msgStreamSub = msgStream.subscribe({ + error: (e) => {}, + complete: () => { + msgStreamSub.unsubscribe(); + }, + }); return { xhr, - stream, + stream: msgStream, }; } diff --git a/src/plugins/bfetch/public/streaming/index.ts b/src/plugins/bfetch/public/streaming/index.ts index afb442feffb294..545cae87aa3d6a 100644 --- a/src/plugins/bfetch/public/streaming/index.ts +++ b/src/plugins/bfetch/public/streaming/index.ts @@ -9,3 +9,4 @@ export * from './split'; export * from './from_streaming_xhr'; export * from './fetch_streaming'; +export { inflateResponse } from './inflate_response'; diff --git a/src/plugins/vis_type_timeseries/common/get_last_value.js b/src/plugins/bfetch/public/streaming/inflate_response.ts similarity index 54% rename from src/plugins/vis_type_timeseries/common/get_last_value.js rename to src/plugins/bfetch/public/streaming/inflate_response.ts index 80adf7098f24dd..73cb52285987ca 100644 --- a/src/plugins/vis_type_timeseries/common/get_last_value.js +++ b/src/plugins/bfetch/public/streaming/inflate_response.ts @@ -6,16 +6,10 @@ * Side Public License, v 1. */ -import { isArray, last } from 'lodash'; +import { unzlibSync, strFromU8 } from 'fflate'; -export const DEFAULT_VALUE = '-'; - -const extractValue = (data) => (data && data[1]) ?? null; - -export const getLastValue = (data) => { - if (!isArray(data)) { - return data ?? DEFAULT_VALUE; - } - - return extractValue(last(data)) ?? DEFAULT_VALUE; -}; +export function inflateResponse(response: string) { + const buff = Buffer.from(response, 'base64'); + const unzip = unzlibSync(buff); + return strFromU8(unzip); +} diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 18f0813260f036..7fd46e2f6cc44e 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -16,6 +16,7 @@ import type { RouteMethod, RequestHandler, RequestHandlerContext, + StartServicesAccessor, } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { Subject } from 'rxjs'; @@ -28,7 +29,8 @@ import { normalizeError, } from '../common'; import { StreamingRequestHandler } from './types'; -import { createNDJSONStream } from './streaming'; +import { createStream } from './streaming'; +import { getUiSettings } from './ui_settings'; // eslint-disable-next-line export interface BfetchServerSetupDependencies {} @@ -112,9 +114,19 @@ export class BfetchServerPlugin public setup(core: CoreSetup, plugins: BfetchServerSetupDependencies): BfetchServerSetup { const logger = this.initializerContext.logger.get(); const router = core.http.createRouter(); - const addStreamingResponseRoute = this.addStreamingResponseRoute({ router, logger }); + + core.uiSettings.register(getUiSettings()); + + const addStreamingResponseRoute = this.addStreamingResponseRoute({ + getStartServices: core.getStartServices, + router, + logger, + }); const addBatchProcessingRoute = this.addBatchProcessingRoute(addStreamingResponseRoute); - const createStreamingRequestHandler = this.createStreamingRequestHandler({ logger }); + const createStreamingRequestHandler = this.createStreamingRequestHandler({ + getStartServices: core.getStartServices, + logger, + }); return { addBatchProcessingRoute, @@ -129,10 +141,16 @@ export class BfetchServerPlugin public stop() {} + private getCompressionDisabled(request: KibanaRequest) { + return request.headers['x-chunk-encoding'] !== 'deflate'; + } + private addStreamingResponseRoute = ({ + getStartServices, router, logger, }: { + getStartServices: StartServicesAccessor; router: ReturnType; logger: Logger; }): BfetchServerSetup['addStreamingResponseRoute'] => (path, handler) => { @@ -146,9 +164,10 @@ export class BfetchServerPlugin async (context, request, response) => { const handlerInstance = handler(request); const data = request.body; + const compressionDisabled = this.getCompressionDisabled(request); return response.ok({ headers: streamingHeaders, - body: createNDJSONStream(handlerInstance.getResponseStream(data), logger), + body: createStream(handlerInstance.getResponseStream(data), logger, compressionDisabled), }); } ); @@ -156,17 +175,20 @@ export class BfetchServerPlugin private createStreamingRequestHandler = ({ logger, + getStartServices, }: { logger: Logger; + getStartServices: StartServicesAccessor; }): BfetchServerSetup['createStreamingRequestHandler'] => (streamHandler) => async ( context, request, response ) => { const response$ = await streamHandler(context, request); + const compressionDisabled = this.getCompressionDisabled(request); return response.ok({ headers: streamingHeaders, - body: createNDJSONStream(response$, logger), + body: createStream(response$, logger, compressionDisabled), }); }; diff --git a/src/plugins/bfetch/server/streaming/create_compressed_stream.ts b/src/plugins/bfetch/server/streaming/create_compressed_stream.ts new file mode 100644 index 00000000000000..6814ed1dd79550 --- /dev/null +++ b/src/plugins/bfetch/server/streaming/create_compressed_stream.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { promisify } from 'util'; +import { Observable } from 'rxjs'; +import { catchError, concatMap, finalize } from 'rxjs/operators'; +import { Logger } from 'src/core/server'; +import { Stream, PassThrough } from 'stream'; +import { constants, deflate } from 'zlib'; + +const delimiter = '\n'; +const pDeflate = promisify(deflate); + +async function zipMessageToStream(output: PassThrough, message: string) { + return new Promise(async (resolve, reject) => { + try { + const gzipped = await pDeflate(message, { + flush: constants.Z_SYNC_FLUSH, + }); + output.write(gzipped.toString('base64')); + output.write(delimiter); + resolve(undefined); + } catch (err) { + reject(err); + } + }); +} + +export const createCompressedStream = ( + results: Observable, + logger: Logger +): Stream => { + const output = new PassThrough(); + + const sub = results + .pipe( + concatMap((message: Response) => { + const strMessage = JSON.stringify(message); + return zipMessageToStream(output, strMessage); + }), + catchError((e) => { + logger.error('Could not serialize or stream a message.'); + logger.error(e); + throw e; + }), + finalize(() => { + output.end(); + sub.unsubscribe(); + }) + ) + .subscribe(); + + return output; +}; diff --git a/src/plugins/bfetch/server/streaming/create_stream.ts b/src/plugins/bfetch/server/streaming/create_stream.ts new file mode 100644 index 00000000000000..7d6981294341b3 --- /dev/null +++ b/src/plugins/bfetch/server/streaming/create_stream.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Logger } from 'kibana/server'; +import { Stream } from 'stream'; +import { Observable } from 'rxjs'; +import { createCompressedStream } from './create_compressed_stream'; +import { createNDJSONStream } from './create_ndjson_stream'; + +export function createStream( + response$: Observable, + logger: Logger, + compressionDisabled: boolean +): Stream { + return compressionDisabled + ? createNDJSONStream(response$, logger) + : createCompressedStream(response$, logger); +} diff --git a/src/plugins/bfetch/server/streaming/index.ts b/src/plugins/bfetch/server/streaming/index.ts index 2c31cc329295d0..dfd472b5034a1b 100644 --- a/src/plugins/bfetch/server/streaming/index.ts +++ b/src/plugins/bfetch/server/streaming/index.ts @@ -7,3 +7,5 @@ */ export * from './create_ndjson_stream'; +export * from './create_compressed_stream'; +export * from './create_stream'; diff --git a/src/plugins/bfetch/server/ui_settings.ts b/src/plugins/bfetch/server/ui_settings.ts new file mode 100644 index 00000000000000..cf7b13a9af1820 --- /dev/null +++ b/src/plugins/bfetch/server/ui_settings.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { UiSettingsParams } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; +import { DISABLE_BFETCH_COMPRESSION } from '../common'; + +export function getUiSettings(): Record> { + return { + [DISABLE_BFETCH_COMPRESSION]: { + name: i18n.translate('bfetch.disableBfetchCompression', { + defaultMessage: 'Disable Batch Compression', + }), + value: false, + description: i18n.translate('bfetch.disableBfetchCompressionDesc', { + defaultMessage: + 'Disable batch compression. This allows you to debug individual requests, but increases response size.', + }), + schema: schema.boolean(), + category: [], + }, + }; +} diff --git a/src/plugins/charts/common/palette.test.ts b/src/plugins/charts/common/palette.test.ts index 0a26d71a9b9d53..86ba74d409cc6e 100644 --- a/src/plugins/charts/common/palette.test.ts +++ b/src/plugins/charts/common/palette.test.ts @@ -12,13 +12,14 @@ import { systemPalette, PaletteOutput, CustomPaletteState, + CustomPaletteArguments, } from './palette'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; describe('palette', () => { const fn = functionWrapper(palette()) as ( context: null, - args?: { color?: string[]; gradient?: boolean; reverse?: boolean } + args?: Partial ) => PaletteOutput; it('results a palette', () => { @@ -39,6 +40,18 @@ describe('palette', () => { }); }); + describe('stop', () => { + it('sets stops', () => { + const result = fn(null, { color: ['red', 'green', 'blue'], stop: [1, 2, 3] }); + expect(result.params!.stops).toEqual([1, 2, 3]); + }); + + it('defaults to pault_tor_14 colors', () => { + const result = fn(null); + expect(result.params!.colors).toEqual(defaultCustomColors); + }); + }); + describe('gradient', () => { it('sets gradient', () => { let result = fn(null, { gradient: true }); @@ -69,6 +82,16 @@ describe('palette', () => { const result = fn(null); expect(result.params!.colors).toEqual(defaultCustomColors); }); + + it('keeps the stops order pristine when set', () => { + const stops = [1, 2, 3]; + const result = fn(null, { + color: ['red', 'green', 'blue'], + stop: [1, 2, 3], + reverse: true, + }); + expect(result.params!.stops).toEqual(stops); + }); }); }); }); diff --git a/src/plugins/charts/common/palette.ts b/src/plugins/charts/common/palette.ts index c9232b22cfae14..78c6fcc8120284 100644 --- a/src/plugins/charts/common/palette.ts +++ b/src/plugins/charts/common/palette.ts @@ -14,11 +14,21 @@ export interface CustomPaletteArguments { color?: string[]; gradient: boolean; reverse?: boolean; + stop?: number[]; + range?: 'number' | 'percent'; + rangeMin?: number; + rangeMax?: number; + continuity?: 'above' | 'below' | 'all' | 'none'; } export interface CustomPaletteState { colors: string[]; gradient: boolean; + stops: number[]; + range: 'number' | 'percent'; + rangeMin: number; + rangeMax: number; + continuity?: 'above' | 'below' | 'all' | 'none'; } export interface SystemPaletteArguments { @@ -83,6 +93,35 @@ export function palette(): ExpressionFunctionDefinition< }), required: false, }, + stop: { + multi: true, + types: ['number'], + help: i18n.translate('charts.functions.palette.args.stopHelpText', { + defaultMessage: + 'The palette color stops. When used, it must be associated with each color.', + }), + required: false, + }, + continuity: { + types: ['string'], + options: ['above', 'below', 'all', 'none'], + default: 'above', + help: '', + }, + rangeMin: { + types: ['number'], + help: '', + }, + rangeMax: { + types: ['number'], + help: '', + }, + range: { + types: ['string'], + options: ['number', 'percent'], + default: 'percent', + help: '', + }, gradient: { types: ['boolean'], default: false, @@ -101,15 +140,32 @@ export function palette(): ExpressionFunctionDefinition< }, }, fn: (input, args) => { - const { color, reverse, gradient } = args; + const { + color, + continuity, + reverse, + gradient, + stop, + range, + rangeMin = 0, + rangeMax = 100, + } = args; const colors = ([] as string[]).concat(color || defaultCustomColors); - + const stops = ([] as number[]).concat(stop || []); + if (stops.length > 0 && colors.length !== stops.length) { + throw Error('When stop is used, each color must have an associated stop value.'); + } return { type: 'palette', name: 'custom', params: { colors: reverse ? colors.reverse() : colors, + stops, + range: range ?? 'percent', gradient, + continuity, + rangeMin, + rangeMax, }, }; }, diff --git a/src/plugins/charts/public/services/palettes/helpers.test.ts b/src/plugins/charts/public/services/palettes/helpers.test.ts new file mode 100644 index 00000000000000..90f5745570cc8b --- /dev/null +++ b/src/plugins/charts/public/services/palettes/helpers.test.ts @@ -0,0 +1,307 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { workoutColorForValue } from './helpers'; +import { CustomPaletteState } from '../..'; + +describe('workoutColorForValue', () => { + it('should return no color for empty value', () => { + expect( + workoutColorForValue( + undefined, + { + continuity: 'above', + colors: ['red', 'green', 'blue', 'yellow'], + range: 'number', + gradient: false, + rangeMin: 0, + rangeMax: 200, + stops: [], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + }); + + describe('range: "number"', () => { + const DEFAULT_PROPS: CustomPaletteState = { + continuity: 'above', + colors: ['red', 'green', 'blue', 'yellow'], + range: 'number', + gradient: false, + rangeMin: 0, + rangeMax: 200, + stops: [], + }; + it('find the right color for predefined palettes', () => { + expect(workoutColorForValue(123, DEFAULT_PROPS, { min: 0, max: 200 })).toBe('blue'); + }); + + it('find the right color for custom stops palettes', () => { + expect( + workoutColorForValue( + 50, + { + ...DEFAULT_PROPS, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('blue'); + }); + + it('find the right color for custom stops palettes when value is higher than rangeMax', () => { + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('yellow'); + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + continuity: 'all', + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('yellow'); + }); + + it('returns no color if the value if higher than rangeMax and continuity is nor "above" or "all"', () => { + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + continuity: 'below', + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + continuity: 'none', + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + }); + + it('find the right color for custom stops palettes when value is lower than rangeMin', () => { + expect( + workoutColorForValue( + 10, + { + ...DEFAULT_PROPS, + continuity: 'below', + rangeMin: 20, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('red'); + expect( + workoutColorForValue( + 10, + { + ...DEFAULT_PROPS, + continuity: 'all', + rangeMin: 20, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('red'); + }); + + it('returns no color if the value if lower than rangeMin and continuity is nor "below" or "all"', () => { + expect( + workoutColorForValue( + 0, + { + ...DEFAULT_PROPS, + rangeMin: 10, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + expect( + workoutColorForValue( + 0, + { + ...DEFAULT_PROPS, + continuity: 'none', + rangeMin: 10, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + }); + }); + + describe('range: "percent"', () => { + const DEFAULT_PROPS: CustomPaletteState = { + continuity: 'above', + colors: ['red', 'green', 'blue', 'yellow'], + range: 'percent', + gradient: false, + rangeMin: 0, + rangeMax: 100, + stops: [], + }; + it('find the right color for predefined palettes', () => { + expect(workoutColorForValue(123, DEFAULT_PROPS, { min: 0, max: 200 })).toBe('blue'); + }); + + it('find the right color for custom stops palettes', () => { + expect( + workoutColorForValue( + 113, + { + ...DEFAULT_PROPS, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('blue'); // 113/200 ~ 56% + }); + + it('find the right color for custom stops palettes when value is higher than rangeMax', () => { + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + rangeMax: 90, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('yellow'); + expect( + workoutColorForValue( + 123, + { + ...DEFAULT_PROPS, + continuity: 'all', + rangeMax: 90, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('yellow'); + }); + + it('returns no color if the value if higher than rangeMax and continuity is nor "above" or "all"', () => { + expect( + workoutColorForValue( + 190, + { + ...DEFAULT_PROPS, + continuity: 'below', + rangeMax: 90, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + expect( + workoutColorForValue( + 190, + { + ...DEFAULT_PROPS, + continuity: 'none', + rangeMax: 90, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + }); + + it('find the right color for custom stops palettes when value is lower than rangeMin', () => { + expect( + workoutColorForValue( + 10, + { + ...DEFAULT_PROPS, + continuity: 'below', + rangeMin: 20, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('red'); + expect( + workoutColorForValue( + 10, + { + ...DEFAULT_PROPS, + continuity: 'all', + rangeMin: 20, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBe('red'); + }); + + it('returns no color if the value if lower than rangeMin and continuity is nor "below" or "all"', () => { + expect( + workoutColorForValue( + 0, + { + ...DEFAULT_PROPS, + continuity: 'above', + rangeMin: 10, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + expect( + workoutColorForValue( + 0, + { + ...DEFAULT_PROPS, + continuity: 'none', + rangeMin: 10, + rangeMax: 100, + stops: [20, 40, 60, 80], + }, + { min: 0, max: 200 } + ) + ).toBeUndefined(); + }); + }); +}); diff --git a/src/plugins/charts/public/services/palettes/helpers.ts b/src/plugins/charts/public/services/palettes/helpers.ts new file mode 100644 index 00000000000000..d4b1e98f94cc8a --- /dev/null +++ b/src/plugins/charts/public/services/palettes/helpers.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CustomPaletteState } from '../..'; + +function findColorSegment( + value: number, + comparison: (value: number, bucket: number) => number, + colors: string[], + rangeMin: number, + rangeMax: number +) { + // assume uniform distribution within the provided range, can ignore stops + const step = (rangeMax - rangeMin) / colors.length; + + // what about values in range + const index = colors.findIndex((c, i) => comparison(value, rangeMin + (1 + i) * step) <= 0); + return colors[index] || colors[0]; +} + +function findColorsByStops( + value: number, + comparison: (value: number, bucket: number) => number, + colors: string[], + stops: number[] +) { + const index = stops.findIndex((s) => comparison(value, s) < 0); + return colors[index] || colors[0]; +} + +function getNormalizedValueByRange( + value: number, + { range }: CustomPaletteState, + minMax: { min: number; max: number } +) { + let result = value; + if (range === 'percent') { + result = (100 * (value - minMax.min)) / (minMax.max - minMax.min); + } + // for a range of 1 value the formulas above will divide by 0, so here's a safety guard + if (Number.isNaN(result)) { + return 1; + } + return result; +} + +/** + * When stops are empty, it is assumed a predefined palette, so colors are distributed uniformly in the whole data range + * When stops are passed, then rangeMin/rangeMax are used as reference for user defined limits: + * continuity is defined over rangeMin/rangeMax, not these stops values (rangeMin/rangeMax are computed from user's stop inputs) + */ +export function workoutColorForValue( + value: number | undefined, + params: CustomPaletteState, + minMax: { min: number; max: number } +) { + if (value == null) { + return; + } + const { colors, stops, range = 'percent', continuity = 'above', rangeMax, rangeMin } = params; + // ranges can be absolute numbers or percentages + // normalized the incoming value to the same format as range to make easier comparisons + const normalizedValue = getNormalizedValueByRange(value, params, minMax); + const dataRangeArguments = range === 'percent' ? [0, 100] : [minMax.min, minMax.max]; + const comparisonFn = (v: number, threshold: number) => v - threshold; + + // if steps are defined consider the specific rangeMax/Min as data boundaries + const maxRange = stops.length ? rangeMax : dataRangeArguments[1]; + const minRange = stops.length ? rangeMin : dataRangeArguments[0]; + + // in case of shorter rangers, extends the steps on the sides to cover the whole set + if (comparisonFn(normalizedValue, maxRange) > 0) { + if (continuity === 'above' || continuity === 'all') { + return colors[colors.length - 1]; + } + return; + } + if (comparisonFn(normalizedValue, minRange) < 0) { + if (continuity === 'below' || continuity === 'all') { + return colors[0]; + } + return; + } + + if (stops.length) { + return findColorsByStops(normalizedValue, comparisonFn, colors, stops); + } + + return findColorSegment( + normalizedValue, + comparisonFn, + colors, + dataRangeArguments[0], + dataRangeArguments[1] + ); +} diff --git a/src/plugins/charts/public/services/palettes/mock.ts b/src/plugins/charts/public/services/palettes/mock.ts index 1c112ec800c922..e94f47477ab11e 100644 --- a/src/plugins/charts/public/services/palettes/mock.ts +++ b/src/plugins/charts/public/services/palettes/mock.ts @@ -14,8 +14,8 @@ export const getPaletteRegistry = () => { const mockPalette1: jest.Mocked = { id: 'default', title: 'My Palette', - getColor: jest.fn((_: SeriesLayer[]) => 'black'), - getColors: jest.fn((num: number) => ['red', 'black']), + getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'black'), + getCategoricalColors: jest.fn((num: number) => ['red', 'black']), toExpression: jest.fn(() => ({ type: 'expression', chain: [ @@ -33,8 +33,32 @@ export const getPaletteRegistry = () => { const mockPalette2: jest.Mocked = { id: 'mocked', title: 'Mocked Palette', - getColor: jest.fn((_: SeriesLayer[]) => 'blue'), - getColors: jest.fn((num: number) => ['blue', 'yellow']), + getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'), + getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']), + toExpression: jest.fn(() => ({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'system_palette', + arguments: { + name: ['mocked'], + }, + }, + ], + })), + }; + + const mockPalette3: jest.Mocked = { + id: 'custom', + title: 'Custom Mocked Palette', + getCategoricalColor: jest.fn((_: SeriesLayer[]) => 'blue'), + getCategoricalColors: jest.fn((num: number) => ['blue', 'yellow']), + getColorForValue: jest.fn( + (num: number | undefined, state: unknown, minMax: { min: number; max: number }) => + num == null || num < 1 ? undefined : 'blue' + ), + canDynamicColoring: true, toExpression: jest.fn(() => ({ type: 'expression', chain: [ @@ -50,8 +74,9 @@ export const getPaletteRegistry = () => { }; return { - get: (name: string) => (name !== 'default' ? mockPalette2 : mockPalette1), - getAll: () => [mockPalette1, mockPalette2], + get: (name: string) => + name === 'custom' ? mockPalette3 : name !== 'default' ? mockPalette2 : mockPalette1, + getAll: () => [mockPalette1, mockPalette2, mockPalette3], }; }; diff --git a/src/plugins/charts/public/services/palettes/palettes.test.tsx b/src/plugins/charts/public/services/palettes/palettes.test.tsx index 8f495df7f882af..8cb477b0e0838b 100644 --- a/src/plugins/charts/public/services/palettes/palettes.test.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.test.tsx @@ -19,14 +19,14 @@ describe('palettes', () => { it('should return different colors based on behind text flag', () => { const palette = palettes.default; - const color1 = palette.getColor([ + const color1 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 0, totalSeriesAtDepth: 5, }, ]); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'abc', @@ -44,14 +44,14 @@ describe('palettes', () => { it('should return different colors based on rank at current series', () => { const palette = palettes.default; - const color1 = palette.getColor([ + const color1 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 0, totalSeriesAtDepth: 5, }, ]); - const color2 = palette.getColor([ + const color2 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 1, @@ -64,7 +64,7 @@ describe('palettes', () => { it('should return the same color for different positions on outer series layers', () => { const palette = palettes.default; - const color1 = palette.getColor([ + const color1 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 0, @@ -76,7 +76,7 @@ describe('palettes', () => { totalSeriesAtDepth: 2, }, ]); - const color2 = palette.getColor([ + const color2 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 0, @@ -96,7 +96,7 @@ describe('palettes', () => { it('should return different colors based on behind text flag', () => { const palette = palettes.default; - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'abc', @@ -108,7 +108,7 @@ describe('palettes', () => { syncColors: true, } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'abc', @@ -127,7 +127,7 @@ describe('palettes', () => { it('should return different colors for different keys', () => { const palette = palettes.default; - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'abc', @@ -139,7 +139,7 @@ describe('palettes', () => { syncColors: true, } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'def', @@ -157,7 +157,7 @@ describe('palettes', () => { it('should return the same color for the same key, irregardless of rank', () => { const palette = palettes.default; - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'hij', @@ -169,7 +169,7 @@ describe('palettes', () => { syncColors: true, } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'hij', @@ -187,7 +187,7 @@ describe('palettes', () => { it('should return the same color for different positions on outer series layers', () => { const palette = palettes.default; - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'klm', @@ -204,7 +204,7 @@ describe('palettes', () => { syncColors: true, } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'klm', @@ -227,7 +227,7 @@ describe('palettes', () => { it('should return the same index of the behind text palette for same key', () => { const palette = palettes.default; - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'klm', @@ -244,7 +244,7 @@ describe('palettes', () => { syncColors: true, } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'klm', @@ -273,15 +273,15 @@ describe('palettes', () => { const palette = palettes.warm; it('should use the whole gradient', () => { - const wholePalette = palette.getColors(10); - const color1 = palette.getColor([ + const wholePalette = palette.getCategoricalColors(10); + const color1 = palette.getCategoricalColor([ { name: 'abc', rankAtDepth: 0, totalSeriesAtDepth: 10, }, ]); - const color2 = palette.getColor([ + const color2 = palette.getCategoricalColor([ { name: 'def', rankAtDepth: 9, @@ -304,7 +304,7 @@ describe('palettes', () => { describe('syncColors: false', () => { it('should not query legacy color service', () => { - palette.getColor( + palette.getCategoricalColor( [ { name: 'abc', @@ -323,7 +323,7 @@ describe('palettes', () => { it('should respect the advanced settings color mapping', () => { const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; configColorGetter.mockImplementation(() => 'blue'); - const result = palette.getColor( + const result = palette.getCategoricalColor( [ { name: 'abc', @@ -345,7 +345,7 @@ describe('palettes', () => { }); it('should return a color from the legacy palette based on position of first series', () => { - const result = palette.getColor( + const result = palette.getCategoricalColor( [ { name: 'abc', @@ -368,7 +368,7 @@ describe('palettes', () => { describe('syncColors: true', () => { it('should query legacy color service', () => { - palette.getColor( + palette.getCategoricalColor( [ { name: 'abc', @@ -387,7 +387,7 @@ describe('palettes', () => { it('should respect the advanced settings color mapping', () => { const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; configColorGetter.mockImplementation(() => 'blue'); - const result = palette.getColor( + const result = palette.getCategoricalColor( [ { name: 'abc', @@ -409,7 +409,7 @@ describe('palettes', () => { }); it('should always use root series', () => { - palette.getColor( + palette.getCategoricalColor( [ { name: 'abc', @@ -437,7 +437,7 @@ describe('palettes', () => { describe('custom palette', () => { const palette = palettes.custom; it('should return different colors based on rank at current series', () => { - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'abc', @@ -450,7 +450,7 @@ describe('palettes', () => { colors: ['#00ff00', '#000000'], } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'abc', @@ -467,7 +467,7 @@ describe('palettes', () => { }); it('should return the same color for different positions on outer series layers', () => { - const color1 = palette.getColor( + const color1 = palette.getCategoricalColor( [ { name: 'abc', @@ -485,7 +485,7 @@ describe('palettes', () => { colors: ['#00ff00', '#000000'], } ); - const color2 = palette.getColor( + const color2 = palette.getCategoricalColor( [ { name: 'abc', @@ -507,7 +507,7 @@ describe('palettes', () => { }); it('should use passed in colors', () => { - const color = palette.getColor( + const color = palette.getCategoricalColor( [ { name: 'abc', @@ -523,5 +523,56 @@ describe('palettes', () => { ); expect(color).toEqual('#00ff00'); }); + + // just an integration test here. More in depth tests on the subject can be found on the helper file + it('should return a color for the given value with its domain', () => { + expect( + palette.getColorForValue!( + 0, + { colors: ['red', 'green', 'blue'], stops: [], gradient: false }, + { min: 0, max: 100 } + ) + ).toBe('red'); + }); + + it('should return a color for the given value with its domain based on custom stops', () => { + expect( + palette.getColorForValue!( + 60, + { + colors: ['red', 'green', 'blue'], + stops: [10, 50, 100], + range: 'percent', + gradient: false, + rangeMin: 0, + rangeMax: 100, + }, + { min: 0, max: 100 } + ) + ).toBe('blue'); + }); + + // just make sure to not have broken anything + it('should work with only legacy arguments, filling with default values the new ones', () => { + expect(palette.toExpression({ colors: [], gradient: false })).toEqual({ + type: 'expression', + chain: [ + { + type: 'function', + function: 'palette', + arguments: { + color: [], + gradient: [false], + reverse: [false], + continuity: ['above'], + stop: [], + range: ['percent'], + rangeMax: [], + rangeMin: [], + }, + }, + ], + }); + }); }); }); diff --git a/src/plugins/charts/public/services/palettes/palettes.tsx b/src/plugins/charts/public/services/palettes/palettes.tsx index b11d598c1c1cbc..65e3f9a84203dd 100644 --- a/src/plugins/charts/public/services/palettes/palettes.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.tsx @@ -30,6 +30,7 @@ import { lightenColor } from './lighten_color'; import { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from './types'; import { LegacyColorsService } from '../legacy_colors'; import { MappedColors } from '../mapped_colors'; +import { workoutColorForValue } from './helpers'; function buildRoundRobinCategoricalWithMappedColors(): Omit { const colors = euiPaletteColorBlind({ rotations: 2 }); @@ -64,8 +65,8 @@ function buildRoundRobinCategoricalWithMappedColors(): Omit euiPaletteColorBlind(), + getCategoricalColor: getColor, + getCategoricalColors: () => euiPaletteColorBlind(), toExpression: () => ({ type: 'expression', chain: [ @@ -102,8 +103,9 @@ function buildGradient( } return { id, - getColor, - getColors: colors, + getCategoricalColor: getColor, + getCategoricalColors: colors, + canDynamicColoring: true, toExpression: () => ({ type: 'expression', chain: [ @@ -141,8 +143,8 @@ function buildSyncedKibanaPalette( } return { id: 'kibana_palette', - getColor, - getColors: () => colors.seedColors.slice(0, 10), + getCategoricalColor: getColor, + getCategoricalColors: () => colors.seedColors.slice(0, 10), toExpression: () => ({ type: 'expression', chain: [ @@ -161,7 +163,24 @@ function buildSyncedKibanaPalette( function buildCustomPalette(): PaletteDefinition { return { id: 'custom', - getColor: ( + getColorForValue: ( + value, + params: { + colors: string[]; + range: 'number' | 'percent'; + continuity: 'above' | 'below' | 'none' | 'all'; + gradient: boolean; + /** Stops values mark where colors end (non-inclusive value) */ + stops: number[]; + /** Important: specify rangeMin/rangeMax if custom stops are defined! */ + rangeMax: number; + rangeMin: number; + }, + dataBounds + ) => { + return workoutColorForValue(value, params, dataBounds); + }, + getCategoricalColor: ( series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = { behindText: false }, { colors, gradient }: { colors: string[]; gradient: boolean } @@ -179,10 +198,48 @@ function buildCustomPalette(): PaletteDefinition { }, internal: true, title: i18n.translate('charts.palettes.customLabel', { defaultMessage: 'Custom' }), - getColors: (size: number, { colors, gradient }: { colors: string[]; gradient: boolean }) => { + getCategoricalColors: ( + size: number, + { + colors, + gradient, + stepped, + stops, + }: { colors: string[]; gradient: boolean; stepped: boolean; stops: number[] } = { + colors: [], + gradient: false, + stepped: false, + stops: [], + } + ) => { + if (stepped) { + const range = stops[stops.length - 1] - stops[0]; + const offset = stops[0]; + const finalStops = [...stops.map((stop) => (stop - offset) / range)]; + return chroma.scale(colors).domain(finalStops).colors(size); + } return gradient ? chroma.scale(colors).colors(size) : colors; }, - toExpression: ({ colors, gradient }: { colors: string[]; gradient: boolean }) => ({ + canDynamicColoring: false, + toExpression: ({ + colors, + gradient, + stops = [], + rangeMax, + rangeMin, + rangeType = 'percent', + continuity = 'above', + reverse = false, + }: { + colors: string[]; + gradient: boolean; + stops: number[]; + rangeMax?: number; + rangeMin?: number; + rangeType: 'percent' | 'number'; + continuity?: 'all' | 'none' | 'above' | 'below'; + reverse?: boolean; + }) => ({ type: 'expression', chain: [ { @@ -191,6 +248,12 @@ function buildCustomPalette(): PaletteDefinition { arguments: { color: colors, gradient: [gradient], + reverse: [reverse], + continuity: [continuity], + stop: stops, + range: [rangeType], + rangeMax: rangeMax == null ? [] : [rangeMax], + rangeMin: rangeMin == null ? [] : [rangeMin], }, }, ], diff --git a/src/plugins/charts/public/services/palettes/types.ts b/src/plugins/charts/public/services/palettes/types.ts index 3d2a6b032f63e9..6f13f621783640 100644 --- a/src/plugins/charts/public/services/palettes/types.ts +++ b/src/plugins/charts/public/services/palettes/types.ts @@ -79,22 +79,12 @@ export interface PaletteDefinition { * @param state The internal state of the palette */ toExpression: (state?: T) => Ast; - /** - * Renders the UI for editing the internal state of the palette. - * Not each palette has to feature an internal state, so this is an optional property. - * @param domElement The dom element to the render the editor UI into - * @param props Current state and state setter to issue updates - */ - renderEditor?: ( - domElement: Element, - props: { state?: T; setState: (updater: (oldState: T) => T) => void } - ) => void; /** * Color a series according to the internal rules of the palette. * @param series The current series along with its ancestors. * @param state The internal state of the palette */ - getColor: ( + getCategoricalColor: ( series: SeriesLayer[], chartConfiguration?: ChartColorConfiguration, state?: T @@ -103,7 +93,20 @@ export interface PaletteDefinition { * Get a spectrum of colors of the current palette. * This can be used if the chart wants to control color assignment locally. */ - getColors: (size: number, state?: T) => string[]; + getCategoricalColors: (size: number, state?: T) => string[]; + /** + * Define whether a palette supports dynamic coloring (i.e. gradient colors mapped to number values) + */ + canDynamicColoring?: boolean; + /** + * Get the assigned color for the given value based on its data domain and state settings. + * This can be used for dynamic coloring based on uniform color distribution or custom stops. + */ + getColorForValue?: ( + value: number | undefined, + state: T, + { min, max }: { min: number; max: number } + ) => string | undefined; } export interface PaletteRegistry { diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index fa86fb81bd4073..93310bb8213615 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -295,13 +295,6 @@ export function DashboardApp({ }; }, [dashboardStateManager, dashboardContainer, onAppLeave, embeddable]); - // clear search session when leaving dashboard route - useEffect(() => { - return () => { - data.search.session.clear(); - }; - }, [data.search.session]); - return ( <> {savedDashboard && dashboardStateManager && dashboardContainer && viewMode && ( diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index d5eddf6bb48647..be279ed98492e7 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -198,8 +198,14 @@ export async function mountApp({ return ; }; - // make sure the index pattern list is up to date - await dataStart.indexPatterns.clearCache(); + const hasEmbeddableIncoming = Boolean( + dashboardServices.embeddable + .getStateTransfer() + .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, false) + ); + if (!hasEmbeddableIncoming) { + dataStart.indexPatterns.clearCache(); + } // dispatch synthetic hash change event to update hash history objects // this is necessary because hash updates triggered by using popState won't trigger this event naturally. @@ -242,7 +248,6 @@ export async function mountApp({ } render(app, element); return () => { - dataStart.search.session.clear(); unlistenParentHistory(); unmountComponentAtNode(element); appUnMounted(); diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts index 0be29f67a94922..d715fb70ec91a8 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts @@ -85,6 +85,7 @@ export const useDashboardContainer = ({ let canceled = false; let pendingContainer: DashboardContainer | ErrorEmbeddable | null | undefined; (async function createContainer() { + const existingSession = searchSession.getSessionId(); pendingContainer = await dashboardFactory.create( getDashboardContainerInput({ isEmbeddedExternally: Boolean(isEmbeddedExternally), @@ -92,7 +93,9 @@ export const useDashboardContainer = ({ dashboardStateManager, incomingEmbeddable, query, - searchSessionId: searchSessionIdFromURL ?? searchSession.start(), + searchSessionId: + searchSessionIdFromURL ?? + (existingSession && incomingEmbeddable ? existingSession : searchSession.start()), }) ); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index db0404595af6c8..e2c11d614d7970 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -83,6 +83,11 @@ export const DashboardListing = ({ }; }, [title, savedObjectsClient, redirectTo, data.query, kbnUrlStateStorage]); + // clear dangling session because they are not required here + useEffect(() => { + data.search.session.clear(); + }, [data.search.session]); + const hideWriteControls = dashboardCapabilities.hideWriteControls; const listingLimit = savedObjects.settings.getListingLimit(); const defaultFilter = title ? `"${title}"` : ''; diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts index c6d923f4505f01..fa9a2c85aaef54 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts +++ b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts @@ -39,12 +39,16 @@ describe('build query', () => { { query: 'extension:jpg', language: 'kuery' }, { query: 'bar:baz', language: 'lucene' }, ] as Query[]; - const filters = [ - { - match_all: {}, - meta: { type: 'match_all' }, - } as MatchAllFilter, - ]; + const filters = { + match: { + a: 'b', + }, + meta: { + alias: '', + disabled: false, + negate: false, + }, + }; const config = { allowLeadingWildcards: true, queryStringOptions: {}, @@ -56,7 +60,11 @@ describe('build query', () => { must: [decorateQuery(luceneStringToDsl('bar:baz'), config.queryStringOptions)], filter: [ toElasticsearchQuery(fromKueryExpression('extension:jpg'), indexPattern), - { match_all: {} }, + { + match: { + a: 'b', + }, + }, ], should: [], must_not: [], @@ -71,9 +79,15 @@ describe('build query', () => { it('should accept queries and filters as either single objects or arrays', () => { const queries = { query: 'extension:jpg', language: 'lucene' } as Query; const filters = { - match_all: {}, - meta: { type: 'match_all' }, - } as MatchAllFilter; + match: { + a: 'b', + }, + meta: { + alias: '', + disabled: false, + negate: false, + }, + }; const config = { allowLeadingWildcards: true, queryStringOptions: {}, @@ -83,7 +97,13 @@ describe('build query', () => { const expectedResult = { bool: { must: [decorateQuery(luceneStringToDsl('extension:jpg'), config.queryStringOptions)], - filter: [{ match_all: {} }], + filter: [ + { + match: { + a: 'b', + }, + }, + ], should: [], must_not: [], }, @@ -94,6 +114,49 @@ describe('build query', () => { expect(result).toEqual(expectedResult); }); + it('should remove match_all clauses', () => { + const filters = [ + { + match_all: {}, + meta: { type: 'match_all' }, + } as MatchAllFilter, + { + match: { + a: 'b', + }, + meta: { + alias: '', + disabled: false, + negate: false, + }, + }, + ]; + const config = { + allowLeadingWildcards: true, + queryStringOptions: {}, + ignoreFilterIfFieldNotInIndex: false, + }; + + const expectedResult = { + bool: { + must: [], + filter: [ + { + match: { + a: 'b', + }, + }, + ], + should: [], + must_not: [], + }, + }; + + const result = buildEsQuery(indexPattern, [], filters, config); + + expect(result).toEqual(expectedResult); + }); + it('should use the default time zone set in the Advanced Settings in queries and filters', () => { const queries = [ { query: '@timestamp:"2019-03-23T13:18:00"', language: 'kuery' }, @@ -122,7 +185,6 @@ describe('build query', () => { indexPattern, config ), - { match_all: {} }, ], should: [], must_not: [], diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.ts b/src/plugins/data/common/es_query/es_query/build_es_query.ts index 18b360de9aaa64..45724796c3518c 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.ts +++ b/src/plugins/data/common/es_query/es_query/build_es_query.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { groupBy, has } from 'lodash'; +import { groupBy, has, isEqual } from 'lodash'; import { buildQueryFromKuery } from './from_kuery'; import { buildQueryFromFilters } from './from_filters'; import { buildQueryFromLucene } from './from_lucene'; @@ -21,6 +21,12 @@ export interface EsQueryConfig { dateFormatTZ?: string; } +function removeMatchAll(filters: T[]) { + return filters.filter( + (filter) => !filter || typeof filter !== 'object' || !isEqual(filter, { match_all: {} }) + ); +} + /** * @param indexPattern * @param queries - a query object or array of query objects. Each query has a language property and a query property. @@ -63,9 +69,9 @@ export function buildEsQuery( return { bool: { - must: [...kueryQuery.must, ...luceneQuery.must, ...filterQuery.must], - filter: [...kueryQuery.filter, ...luceneQuery.filter, ...filterQuery.filter], - should: [...kueryQuery.should, ...luceneQuery.should, ...filterQuery.should], + must: removeMatchAll([...kueryQuery.must, ...luceneQuery.must, ...filterQuery.must]), + filter: removeMatchAll([...kueryQuery.filter, ...luceneQuery.filter, ...filterQuery.filter]), + should: removeMatchAll([...kueryQuery.should, ...luceneQuery.should, ...filterQuery.should]), must_not: [...kueryQuery.must_not, ...luceneQuery.must_not, ...filterQuery.must_not], }, }; diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 13a1a1bd388ba6..39680c49483667 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -98,14 +98,6 @@ describe('Session service', () => { expect(nowProvider.reset).toHaveBeenCalled(); }); - it("Can't clear other apps' session", async () => { - sessionService.start(); - expect(sessionService.getSessionId()).not.toBeUndefined(); - currentAppId$.next('change'); - sessionService.clear(); - expect(sessionService.getSessionId()).not.toBeUndefined(); - }); - it("Can start a new session in case there is other apps' stale session", async () => { const s1 = sessionService.start(); expect(sessionService.getSessionId()).not.toBeUndefined(); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 71f51b4bc8d83c..629d76b07d7caf 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -128,21 +128,6 @@ export class SessionService { this.subscription.add( coreStart.application.currentAppId$.subscribe((newAppName) => { this.currentApp = newAppName; - if (!this.getSessionId()) return; - - // Apps required to clean up their sessions before unmounting - // Make sure that apps don't leave sessions open by throwing an error in DEV mode - const message = `Application '${ - this.state.get().appName - }' had an open session while navigating`; - if (initializerContext.env.mode.dev) { - coreStart.fatalErrors.add(message); - } else { - // this should never happen in prod because should be caught in dev mode - // in case this happen we don't want to throw fatal error, as most likely possible bugs are not that critical - // eslint-disable-next-line no-console - console.warn(message); - } }) ); }); @@ -230,18 +215,6 @@ export class SessionService { * Cleans up current state */ public clear() { - // make sure apps can't clear other apps' sessions - const currentSessionApp = this.state.get().appName; - if (currentSessionApp && currentSessionApp !== this.currentApp) { - // eslint-disable-next-line no-console - console.warn( - `Skip clearing session "${this.getSessionId()}" because it belongs to a different app. current: "${ - this.currentApp - }", owner: "${currentSessionApp}"` - ); - return; - } - this.state.transitions.clear(); this.searchSessionInfoProvider = undefined; this.searchSessionIndicatorUiConfig = undefined; diff --git a/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts b/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts index f2377b61b51512..2029354376f261 100644 --- a/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts +++ b/src/plugins/discover/public/application/angular/doc_table/infinite_scroll.ts @@ -34,11 +34,12 @@ export function createInfiniteScrollDirective() { const isMobileView = document.getElementsByClassName('dscSidebar__mobile').length > 0; const usedScrollDiv = isMobileView ? scrollDivMobile : scrollDiv; const scrollTop = usedScrollDiv.scrollTop(); + const scrollOffset = usedScrollDiv.prop('offsetTop') || 0; const winHeight = Number(usedScrollDiv.height()); const winBottom = Number(winHeight) + Number(scrollTop); const elTop = $element.get(0).offsetTop || 0; - const remaining = elTop - winBottom; + const remaining = elTop - scrollOffset - winBottom; if (remaining <= winHeight) { $scope[$scope.$$phase ? '$eval' : '$apply'](function () { diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx index 4aaefc99479c1d..a99819fa9e0576 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx @@ -96,19 +96,6 @@ export function DiscoverGridDocumentToolbarBtn({ /> ), - - { - setIsSelectionPopoverOpen(false); - setSelectedDocs([]); - setIsFilterActive(false); - }} - > - - , )} , + { + setIsSelectionPopoverOpen(false); + setSelectedDocs([]); + setIsFilterActive(false); + }} + > + + , ]; }, [ isFilterActive, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index f1592d5a8cf0b1..5f70deccba93cf 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -388,6 +388,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'long', _meta: { description: 'Non-default value of setting.' }, }, + 'bfetch:disableCompression': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'visualization:visualize:legacyChartsLibrary': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 570b52171be283..bf28bb6cc01f5d 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -22,6 +22,7 @@ export interface UsageStats { /** * non-sensitive settings */ + 'bfetch:disableCompression': boolean; 'autocomplete:useTimeRange': boolean; 'search:timeout': number; 'visualization:visualize:legacyChartsLibrary': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 693957057f1084..0ca1b863f91a7b 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8217,6 +8217,12 @@ "description": "Non-default value of setting." } }, + "bfetch:disableCompression": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "visualization:visualize:legacyChartsLibrary": { "type": "boolean", "_meta": { diff --git a/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx b/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx index b09a806e8fc253..9249edef8af921 100644 --- a/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx @@ -39,12 +39,12 @@ export function PalettePicker({ palettes={palettes .getAll() .filter(({ internal }) => !internal) - .map(({ id, title, getColors }) => { + .map(({ id, title, getCategoricalColors }) => { return { value: id, title, type: 'fixed', - palette: getColors( + palette: getCategoricalColors( 10, id === activePalette?.name ? activePalette?.params : undefined ), diff --git a/src/plugins/vis_type_timeseries/common/get_last_value.test.js b/src/plugins/vis_type_timeseries/common/get_last_value.test.js deleted file mode 100644 index 794bbe17a1e7a1..00000000000000 --- a/src/plugins/vis_type_timeseries/common/get_last_value.test.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getLastValue } from './get_last_value'; - -describe('getLastValue(data)', () => { - test('should returns data if data is not array', () => { - expect(getLastValue('foo')).toBe('foo'); - }); - - test('should returns 0 as a value when not an array', () => { - expect(getLastValue(0)).toBe(0); - }); - - test('should returns the last value', () => { - expect(getLastValue([[1, 2]])).toBe(2); - }); - - test('should return 0 as a valid value', () => { - expect(getLastValue([[0, 0]])).toBe(0); - }); - - test('should returns the default value ', () => { - expect(getLastValue()).toBe('-'); - }); - - test('should returns 0 if second to last is not defined (default)', () => { - expect( - getLastValue([ - [1, null], - [2, null], - ]) - ).toBe('-'); - }); -}); diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts index 7828ee33736eec..a601da234e0782 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts @@ -90,7 +90,7 @@ describe('fetchIndexPattern', () => { ] as IndexPattern[]; const value = await fetchIndexPattern('indexTitle', indexPatternsService, { - fetchKibabaIndexForStringIndexes: true, + fetchKibanaIndexForStringIndexes: true, }); expect(value).toMatchInlineSnapshot(` @@ -104,9 +104,9 @@ describe('fetchIndexPattern', () => { `); }); - test('should return only indexPatternString if Kibana index does not exist (fetchKibabaIndexForStringIndexes is true)', async () => { + test('should return only indexPatternString if Kibana index does not exist (fetchKibanaIndexForStringIndexes is true)', async () => { const value = await fetchIndexPattern('indexTitle', indexPatternsService, { - fetchKibabaIndexForStringIndexes: true, + fetchKibanaIndexForStringIndexes: true, }); expect(value).toMatchInlineSnapshot(` diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts index 152fd5182225be..1224fd33daee34 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts @@ -51,9 +51,9 @@ export const fetchIndexPattern = async ( indexPatternValue: IndexPatternValue | undefined, indexPatternsService: Pick, options: { - fetchKibabaIndexForStringIndexes: boolean; + fetchKibanaIndexForStringIndexes: boolean; } = { - fetchKibabaIndexForStringIndexes: false, + fetchKibanaIndexForStringIndexes: false, } ): Promise => { let indexPattern: FetchedIndexPattern['indexPattern']; @@ -63,7 +63,7 @@ export const fetchIndexPattern = async ( indexPattern = await indexPatternsService.getDefault(); } else { if (isStringTypeIndexPattern(indexPatternValue)) { - if (options.fetchKibabaIndexForStringIndexes) { + if (options.fetchKibanaIndexForStringIndexes) { indexPattern = (await indexPatternsService.find(indexPatternValue)).find( (index) => index.title === indexPatternValue ); diff --git a/src/plugins/vis_type_timeseries/common/last_value_utils.test.ts b/src/plugins/vis_type_timeseries/common/last_value_utils.test.ts new file mode 100644 index 00000000000000..34e1265b9a6a26 --- /dev/null +++ b/src/plugins/vis_type_timeseries/common/last_value_utils.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getLastValue, isEmptyValue, EMPTY_VALUE } from './last_value_utils'; +import { clone } from 'lodash'; + +describe('getLastValue(data)', () => { + test('should return data, if data is not an array', () => { + const data = 'foo'; + expect(getLastValue(data)).toBe(data); + }); + + test('should return 0 as a value, when data is not an array', () => { + expect(getLastValue(0)).toBe(0); + }); + + test('should return the last value', () => { + const lastVal = 2; + expect(getLastValue([[1, lastVal]])).toBe(lastVal); + }); + + test('should return 0 as a valid value', () => { + expect(getLastValue([[0, 0]])).toBe(0); + }); + + test("should return empty value (null), if second array is empty or it's last element is null/undefined (default)", () => { + expect( + getLastValue([ + [1, null], + [2, null], + ]) + ).toBe(EMPTY_VALUE); + + expect( + getLastValue([ + [1, null], + [2, undefined], + ]) + ).toBe(EMPTY_VALUE); + }); +}); + +describe('isEmptyValue(value)', () => { + test('should return true if is equal to the empty value', () => { + // if empty value will change, no need to rewrite test for passing it. + const emptyValue = + typeof EMPTY_VALUE === 'object' && EMPTY_VALUE != null ? clone(EMPTY_VALUE) : EMPTY_VALUE; + expect(isEmptyValue(emptyValue)).toBe(true); + }); + + test('should return the last value', () => { + const notEmptyValue = [...Array(10).keys()]; + expect(isEmptyValue(notEmptyValue)).toBe(false); + }); +}); diff --git a/src/plugins/vis_type_timeseries/common/last_value_utils.ts b/src/plugins/vis_type_timeseries/common/last_value_utils.ts new file mode 100644 index 00000000000000..a51a04962a8918 --- /dev/null +++ b/src/plugins/vis_type_timeseries/common/last_value_utils.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isArray, last, isEqual } from 'lodash'; + +export const EMPTY_VALUE = null; +export const DISPLAY_EMPTY_VALUE = '-'; + +const extractValue = (data: unknown[] | void) => (data && data[1]) ?? EMPTY_VALUE; + +export const getLastValue = (data: unknown) => { + if (!isArray(data)) { + return data; + } + + return extractValue(last(data)); +}; + +export const isEmptyValue = (value: unknown) => isEqual(value, EMPTY_VALUE); diff --git a/src/plugins/vis_type_timeseries/common/operators_utils.test.ts b/src/plugins/vis_type_timeseries/common/operators_utils.test.ts new file mode 100644 index 00000000000000..ad66f058a4918c --- /dev/null +++ b/src/plugins/vis_type_timeseries/common/operators_utils.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getOperator, shouldOperate, Rule, Operator } from './operators_utils'; + +describe('getOperator(operator)', () => { + test('should return operator function', () => { + const operatorName = Operator.Gte; + const operator = getOperator(operatorName); + expect(typeof operator).toBe('function'); + }); +}); + +describe('shouldOperate(rule, value)', () => { + test('should operate, if value is not null and rule value is not null', () => { + const rule: Rule = { + value: 1, + operator: Operator.Gte, + }; + const value = 2; + + expect(shouldOperate(rule, value)).toBeTruthy(); + }); + + test('should operate, if value is null and operator allows null value', () => { + const rule: Rule = { + operator: Operator.Empty, + value: null, + }; + const value = null; + + expect(shouldOperate(rule, value)).toBeTruthy(); + }); + + test("should not operate, if value is null and operator doesn't allow null values", () => { + const rule: Rule = { + operator: Operator.Gte, + value: 2, + }; + const value = null; + + expect(shouldOperate(rule, value)).toBeFalsy(); + }); + + test("should not operate, if rule value is null and operator doesn't allow null values", () => { + const rule: Rule = { + operator: Operator.Gte, + value: null, + }; + const value = 3; + + expect(shouldOperate(rule, value)).toBeFalsy(); + }); +}); diff --git a/src/plugins/vis_type_timeseries/common/operators_utils.ts b/src/plugins/vis_type_timeseries/common/operators_utils.ts new file mode 100644 index 00000000000000..603e63159b22d0 --- /dev/null +++ b/src/plugins/vis_type_timeseries/common/operators_utils.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { gt, gte, lt, lte, isNull } from 'lodash'; + +export enum Operator { + Gte = 'gte', + Lte = 'lte', + Gt = 'gt', + Lt = 'lt', + Empty = 'empty', +} + +export interface Rule { + operator: Operator; + value: unknown; +} + +type OperatorsAllowNullType = { + [name in Operator]?: boolean; +}; + +const OPERATORS = { + [Operator.Gte]: gte, + [Operator.Lte]: lte, + [Operator.Gt]: gt, + [Operator.Lt]: lt, + [Operator.Empty]: isNull, +}; + +const OPERATORS_ALLOW_NULL: OperatorsAllowNullType = { + [Operator.Empty]: true, +}; + +export const getOperator = (operator: Operator) => { + return OPERATORS[operator]; +}; + +// This check is necessary for preventing from comparing null values with numeric rules. +export const shouldOperate = (rule: Rule, value: unknown) => + (isNull(rule.value) && OPERATORS_ALLOW_NULL[rule.operator]) || + (!isNull(rule.value) && !isNull(value)); diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx index 9ea8898636cec4..3b1356d571749c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/color_rules.test.tsx @@ -12,22 +12,51 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test/jest'; import { collectionActions } from './lib/collection_actions'; -import { ColorRules, ColorRulesProps } from './color_rules'; +import { + ColorRules, + ColorRulesProps, + colorRulesOperatorsList, + ColorRulesOperator, +} from './color_rules'; +import { Operator } from '../../../common/operators_utils'; describe('src/legacy/core_plugins/metrics/public/components/color_rules.test.js', () => { - const defaultProps = ({ + const emptyRule: ColorRulesOperator = colorRulesOperatorsList.filter( + (operator) => operator.method === Operator.Empty + )[0]; + const notEmptyRule: ColorRulesOperator = colorRulesOperatorsList.filter( + (operator) => operator.method !== Operator.Empty + )[0]; + + const getColorRulesProps = (gaugeColorRules: unknown = []) => ({ name: 'gauge_color_rules', - model: { - gauge_color_rules: [ - { - gauge: null, - value: 0, - id: 'unique value', - }, - ], - }, + model: { gauge_color_rules: gaugeColorRules }, onChange: jest.fn(), - } as unknown) as ColorRulesProps; + }); + + const defaultProps = (getColorRulesProps([ + { + gauge: null, + value: 0, + id: 'unique value', + }, + ]) as unknown) as ColorRulesProps; + + const emptyColorRuleProps = (getColorRulesProps([ + { + operator: emptyRule?.method, + value: emptyRule?.value, + id: 'unique value', + }, + ]) as unknown) as ColorRulesProps; + + const notEmptyColorRuleProps = (getColorRulesProps([ + { + operator: notEmptyRule?.method, + value: notEmptyRule?.value, + id: 'unique value', + }, + ]) as unknown) as ColorRulesProps; describe('ColorRules', () => { it('should render empty

node', () => { @@ -47,6 +76,7 @@ describe('src/legacy/core_plugins/metrics/public/components/color_rules.test.js' expect(isNode).toBeTruthy(); }); + it('should handle change of operator and value correctly', () => { collectionActions.handleChange = jest.fn(); const wrapper = mountWithIntl(); @@ -57,8 +87,23 @@ describe('src/legacy/core_plugins/metrics/public/components/color_rules.test.js' expect((collectionActions.handleChange as jest.Mock).mock.calls[0][1].operator).toEqual('gt'); const numberInput = findTestSubject(wrapper, 'colorRuleValue'); + numberInput.simulate('change', { target: { value: '123' } }); expect((collectionActions.handleChange as jest.Mock).mock.calls[1][1].value).toEqual(123); }); + + it('should handle render of value field if empty value oparetor is selected by default', () => { + collectionActions.handleChange = jest.fn(); + const wrapper = mountWithIntl(); + const numberInput = findTestSubject(wrapper, 'colorRuleValue'); + expect(numberInput.exists()).toBeFalsy(); + }); + + it('should handle render of value field if not empty operator is selected by default', () => { + collectionActions.handleChange = jest.fn(); + const wrapper = mountWithIntl(); + const numberInput = findTestSubject(wrapper, 'colorRuleValue'); + expect(numberInput.exists()).toBeTruthy(); + }); }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx b/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx index 7aea5f934ee90b..0cc64528ae3f37 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/color_rules.tsx @@ -23,6 +23,7 @@ import { AddDeleteButtons } from './add_delete_buttons'; import { collectionActions } from './lib/collection_actions'; import { ColorPicker, ColorPickerProps } from './color_picker'; import { TimeseriesVisParams } from '../../types'; +import { Operator } from '../../../common/operators_utils'; export interface ColorRulesProps { name: keyof TimeseriesVisParams; @@ -40,10 +41,17 @@ interface ColorRule { id: string; background_color?: string; color?: string; - operator?: string; + operator?: Operator; text?: string; } +export interface ColorRulesOperator { + label: string; + method: Operator; + value?: unknown; + hideValueSelector?: boolean; +} + const defaultSecondaryName = i18n.translate( 'visTypeTimeseries.colorRules.defaultSecondaryNameLabel', { @@ -54,33 +62,45 @@ const defaultPrimaryName = i18n.translate('visTypeTimeseries.colorRules.defaultP defaultMessage: 'background', }); -const operatorOptions = [ +export const colorRulesOperatorsList: ColorRulesOperator[] = [ { label: i18n.translate('visTypeTimeseries.colorRules.greaterThanLabel', { defaultMessage: '> greater than', }), - value: 'gt', + method: Operator.Gt, }, { label: i18n.translate('visTypeTimeseries.colorRules.greaterThanOrEqualLabel', { defaultMessage: '>= greater than or equal', }), - value: 'gte', + method: Operator.Gte, }, { label: i18n.translate('visTypeTimeseries.colorRules.lessThanLabel', { defaultMessage: '< less than', }), - value: 'lt', + method: Operator.Lt, }, { label: i18n.translate('visTypeTimeseries.colorRules.lessThanOrEqualLabel', { defaultMessage: '<= less than or equal', }), - value: 'lte', + method: Operator.Lte, + }, + { + label: i18n.translate('visTypeTimeseries.colorRules.emptyLabel', { + defaultMessage: 'empty', + }), + method: Operator.Empty, + hideValueSelector: true, }, ]; +const operatorOptions = colorRulesOperatorsList.map((operator) => ({ + label: operator.label, + value: operator.method, +})); + export class ColorRules extends Component { constructor(props: ColorRulesProps) { super(props); @@ -100,9 +120,14 @@ export class ColorRules extends Component { handleOperatorChange = (item: ColorRule) => { return (options: Array>) => { + const selectedOperator = colorRulesOperatorsList.find( + (operator) => options[0]?.value === operator.method + ); + const value = selectedOperator?.value ?? null; collectionActions.handleChange(this.props, { ...item, - operator: options[0].value, + operator: options[0]?.value, + value, }); }; }; @@ -119,7 +144,11 @@ export class ColorRules extends Component { const selectedOperatorOption = operatorOptions.find( (option) => model.operator === option.value ); + const selectedOperator = colorRulesOperatorsList.find( + (operator) => model.operator === operator.method + ); + const hideValueSelectorField = selectedOperator?.hideValueSelector ?? false; const labelStyle = { marginBottom: 0 }; let secondary; @@ -203,19 +232,19 @@ export class ColorRules extends Component { fullWidth /> - - - - - + {!hideValueSelectorField && ( + + + + )} { @@ -118,6 +125,7 @@ export const IndexPattern = ({ }; const model = { ...defaults, ..._model }; + const index = model[indexPatternName]; const intervalValidation = validateIntervalValue(model[intervalName]); const selectedTimeRangeOption = timeRangeOptions.find( @@ -133,11 +141,40 @@ export const IndexPattern = ({ updateControlValidity(intervalName, intervalValidation.isValid); }, [intervalName, intervalValidation.isValid, updateControlValidity]); + useEffect(() => { + async function fetchIndex() { + const { indexPatterns } = getDataStart(); + + setFetchedIndex( + index + ? await fetchIndexPattern(index, indexPatterns, { + fetchKibanaIndexForStringIndexes: true, + }) + : { + indexPattern: undefined, + indexPatternString: undefined, + } + ); + } + + fetchIndex(); + }, [index]); + const toggleIndicatorDisplay = useCallback( () => onChange({ [HIDE_LAST_VALUE_INDICATOR]: !model.hide_last_value_indicator }), [model.hide_last_value_indicator, onChange] ); + const getTimefieldPlaceholder = () => { + if (!model[indexPatternName]) { + return defaultIndex?.timeFieldName; + } + + if (useKibanaIndices) { + return fetchedIndex?.indexPattern?.timeFieldName ?? undefined; + } + }; + return (
{!isTimeSeries && ( @@ -207,6 +244,7 @@ export const IndexPattern = ({ diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js index 478b41810719c7..3616a8c8b348d7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js @@ -8,7 +8,7 @@ import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; -import { getLastValue } from '../../../../common/get_last_value'; +import { getLastValue } from '../../../../common/last_value_utils'; import { emptyLabel } from '../../../../common/empty_label'; import { createTickFormatter } from './tick_formatter'; import { labelDateFormatter } from './label_date_formatter'; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx index ece90d47993093..07edfc2e6e0d70 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx @@ -6,18 +6,15 @@ * Side Public License, v 1. */ -import React, { useState, useContext, useCallback, useEffect } from 'react'; +import React, { useContext, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiText, EuiLink, htmlIdGenerator } from '@elastic/eui'; -import { getCoreStart, getDataStart } from '../../../../services'; +import { getCoreStart } from '../../../../services'; import { PanelModelContext } from '../../../contexts/panel_model_context'; -import { - isStringTypeIndexPattern, - fetchIndexPattern, -} from '../../../../../common/index_patterns_utils'; +import { isStringTypeIndexPattern } from '../../../../../common/index_patterns_utils'; import { FieldTextSelect } from './field_text_select'; import { ComboBoxSelect } from './combo_box_select'; @@ -32,6 +29,7 @@ interface IndexPatternSelectProps { onChange: Function; disabled?: boolean; allowIndexSwitchingMode?: boolean; + fetchedIndex: FetchedIndexPattern | null; } const defaultIndexPatternHelpText = i18n.translate( @@ -57,13 +55,13 @@ export const IndexPatternSelect = ({ indexPatternName, onChange, disabled, + fetchedIndex, allowIndexSwitchingMode, }: IndexPatternSelectProps) => { const htmlId = htmlIdGenerator(); const panelModel = useContext(PanelModelContext); const defaultIndex = useContext(DefaultIndexPatternContext); - const [fetchedIndex, setFetchedIndex] = useState(); const useKibanaIndices = Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY]); const Component = useKibanaIndices ? ComboBoxSelect : FieldTextSelect; @@ -98,25 +96,6 @@ export const IndexPatternSelect = ({ }); }, [fetchedIndex]); - useEffect(() => { - async function fetchIndex() { - const { indexPatterns } = getDataStart(); - - setFetchedIndex( - value - ? await fetchIndexPattern(value, indexPatterns, { - fetchKibabaIndexForStringIndexes: true, - }) - : { - indexPattern: undefined, - indexPatternString: undefined, - } - ); - } - - fetchIndex(); - }, [value]); - if (!fetchedIndex) { return null; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index ac4780e673e07a..70529be78567d0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -8,7 +8,7 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; -import { DEFAULT_VALUE } from '../../../../common/get_last_value'; +import { isEmptyValue, DISPLAY_EMPTY_VALUE } from '../../../../common/last_value_utils'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; import { getFieldFormats } from '../../../services'; @@ -38,12 +38,12 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n } } return (val) => { - let value; - - if (val === DEFAULT_VALUE) { - return val; + if (isEmptyValue(val)) { + return DISPLAY_EMPTY_VALUE; } + let value; + if (!isNumber(val)) { value = val; } else { diff --git a/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx index 20c0b40bb2e542..749d6ca62bfa93 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/palette_picker.tsx @@ -33,12 +33,12 @@ export function PalettePicker({ activePalette, palettes, setPalette, color }: Pa ...palettes .getAll() .filter(({ internal }) => !internal) - .map(({ id, title, getColors }) => { + .map(({ id, title, getCategoricalColors }) => { return { value: id, title, type: 'fixed' as const, - palette: getColors(10), + palette: getCategoricalColors(10), }; }), { @@ -49,7 +49,7 @@ export function PalettePicker({ activePalette, palettes, setPalette, color }: Pa type: 'fixed', palette: palettes .get('custom') - .getColors(10, { colors: [color, finalGradientColor], gradient: true }), + .getCategoricalColors(10, { colors: [color, finalGradientColor], gradient: true }), }, { value: PALETTES.RAINBOW, @@ -59,7 +59,7 @@ export function PalettePicker({ activePalette, palettes, setPalette, color }: Pa type: 'fixed', palette: palettes .get('custom') - .getColors(10, { colors: rainbowColors.slice(0, 10), gradient: false }), + .getCategoricalColors(10, { colors: rainbowColors.slice(0, 10), gradient: false }), }, ]} onChange={(newPalette) => { diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js index a464771b01af32..6140726975cbd5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/vis.js @@ -10,9 +10,10 @@ import PropTypes from 'prop-types'; import React from 'react'; import { visWithSplits } from '../../vis_with_splits'; import { createTickFormatter } from '../../lib/tick_formatter'; -import _, { get, isUndefined, assign, includes } from 'lodash'; +import { get, isUndefined, assign, includes } from 'lodash'; import { Gauge } from '../../../visualizations/views/gauge'; -import { getLastValue } from '../../../../../common/get_last_value'; +import { getLastValue } from '../../../../../common/last_value_utils'; +import { getOperator, shouldOperate } from '../../../../../common/operators_utils'; function getColors(props) { const { model, visData } = props; @@ -21,9 +22,9 @@ function getColors(props) { let gauge; if (model.gauge_color_rules) { model.gauge_color_rules.forEach((rule) => { - if (rule.operator && rule.value != null) { - const value = (series[0] && getLastValue(series[0].data)) || 0; - if (_[rule.operator](value, rule.value)) { + if (rule.operator) { + const value = getLastValue(series[0]?.data); + if (shouldOperate(rule, value) && getOperator(rule.operator)(value, rule.value)) { gauge = rule.gauge; text = rule.text; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js index 3029bba04b4500..b35ee977d3e44d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/vis.js @@ -10,10 +10,11 @@ import PropTypes from 'prop-types'; import React from 'react'; import { visWithSplits } from '../../vis_with_splits'; import { createTickFormatter } from '../../lib/tick_formatter'; -import _, { get, isUndefined, assign, includes, pick } from 'lodash'; +import { get, isUndefined, assign, includes, pick } from 'lodash'; import { Metric } from '../../../visualizations/views/metric'; -import { getLastValue } from '../../../../../common/get_last_value'; +import { getLastValue } from '../../../../../common/last_value_utils'; import { isBackgroundInverted } from '../../../lib/set_is_reversed'; +import { getOperator, shouldOperate } from '../../../../../common/operators_utils'; function getColors(props) { const { model, visData } = props; @@ -22,9 +23,9 @@ function getColors(props) { let background; if (model.background_color_rules) { model.background_color_rules.forEach((rule) => { - if (rule.operator && rule.value != null) { - const value = (series[0] && getLastValue(series[0].data)) || 0; - if (_[rule.operator](value, rule.value)) { + if (rule.operator) { + const value = getLastValue(series[0]?.data); + if (shouldOperate(rule, value) && getOperator(rule.operator)(value, rule.value)) { background = rule.background_color; color = rule.color; } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js index 41e6236cbc39b2..0b3a24615c0e30 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/vis.js @@ -9,13 +9,13 @@ import { getCoreStart } from '../../../../services'; import { createTickFormatter } from '../../lib/tick_formatter'; import { TopN } from '../../../visualizations/views/top_n'; -import { getLastValue } from '../../../../../common/get_last_value'; +import { getLastValue } from '../../../../../common/last_value_utils'; import { isBackgroundInverted } from '../../../lib/set_is_reversed'; import { replaceVars } from '../../lib/replace_vars'; import PropTypes from 'prop-types'; import React from 'react'; -import { sortBy, first, get, gt, gte, lt, lte } from 'lodash'; -const OPERATORS = { gt, gte, lt, lte }; +import { sortBy, first, get } from 'lodash'; +import { getOperator, shouldOperate } from '../../../../../common/operators_utils'; function sortByDirection(data, direction, fn) { if (direction === 'desc') { @@ -53,8 +53,8 @@ function TopNVisualization(props) { let color = item.color || seriesConfig.color; if (model.bar_color_rules) { model.bar_color_rules.forEach((rule) => { - if (rule.operator && rule.value != null && rule.bar_color) { - if (OPERATORS[rule.operator](value, rule.value)) { + if (shouldOperate(rule, value) && rule.operator && rule.bar_color) { + if (getOperator(rule.operator)(value, rule.value)) { color = rule.bar_color; } } diff --git a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts index adcf1f3ad63cdb..028ce3d028997c 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts +++ b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts @@ -58,7 +58,7 @@ export const getSplitByTermsColor = ({ } : seriesPalette.params; - const outputColor = palettesRegistry?.get(paletteName || 'default').getColor( + const outputColor = palettesRegistry?.get(paletteName || 'default').getCategoricalColor( [ { name: seriesName || emptyLabel, diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js index 31ea3412972e8f..000701c3a0764a 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge.js @@ -11,7 +11,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import classNames from 'classnames'; import { isBackgroundInverted, isBackgroundDark } from '../../lib/set_is_reversed'; -import { getLastValue } from '../../../../common/get_last_value'; +import { getLastValue } from '../../../../common/last_value_utils'; import { getValueBy } from '../lib/get_value_by'; import { GaugeVis } from './gauge_vis'; import reactcss from 'reactcss'; @@ -61,7 +61,7 @@ export class Gauge extends Component { render() { const { metric, type } = this.props; const { scale, translateX, translateY } = this.state; - const value = metric && getLastValue(metric.data); + const value = getLastValue(metric?.data); const max = (metric && getValueBy('max', metric.data)) || 1; const formatter = (metric && (metric.tickFormatter || metric.formatter)) || @@ -76,16 +76,13 @@ export class Gauge extends Component { left: this.state.left || 0, transform: `matrix(${scale}, 0, 0, ${scale}, ${translateX}, ${translateY})`, }, - }, - valueColor: { - value: { + valueColor: { color: this.props.valueColor, }, }, }, this.props ); - const gaugeProps = { value, reversed: isBackgroundDark(this.props.backgroundColor), @@ -114,7 +111,7 @@ export class Gauge extends Component {
{title}
-
+
{formatter(value)}
{additionalLabel} @@ -127,7 +124,7 @@ export class Gauge extends Component { ref={(el) => (this.inner = el)} style={styles.inner} > -
+
{formatter(value)}
diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js index c8789f98969f88..30b7844a90fdac 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/gauge_vis.js @@ -12,6 +12,7 @@ import _ from 'lodash'; import reactcss from 'reactcss'; import { calculateCoordinates } from '../lib/calculate_coordinates'; import { COLORS } from '../constants/chart'; +import { isEmptyValue } from '../../../../common/last_value_utils'; export class GaugeVis extends Component { constructor(props) { @@ -55,10 +56,14 @@ export class GaugeVis extends Component { render() { const { type, value, max, color } = this.props; + + // if value is empty array, no metrics to display. + const formattedValue = isEmptyValue(value) ? 1 : value; + const { scale, translateX, translateY } = this.state; const size = 2 * Math.PI * 50; const sliceSize = type === 'half' ? 0.6 : 1; - const percent = value < max ? value / max : 1; + const percent = formattedValue < max ? formattedValue / max : 1; const styles = reactcss( { default: { @@ -161,6 +166,6 @@ GaugeVis.propTypes = { max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), metric: PropTypes.object, reversed: PropTypes.bool, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]), type: PropTypes.oneOf(['half', 'circle']), }; diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js index 17cadb94457b6e..bc4230d0a15efe 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/metric.js @@ -11,7 +11,7 @@ import React, { Component } from 'react'; import _ from 'lodash'; import reactcss from 'reactcss'; -import { getLastValue } from '../../../../common/get_last_value'; +import { getLastValue } from '../../../../common/last_value_utils'; import { calculateCoordinates } from '../lib/calculate_coordinates'; export class Metric extends Component { @@ -58,7 +58,8 @@ export class Metric extends Component { const { metric, secondary } = this.props; const { scale, translateX, translateY } = this.state; const primaryFormatter = (metric && (metric.tickFormatter || metric.formatter)) || ((n) => n); - const primaryValue = primaryFormatter(getLastValue(metric && metric.data)); + const primaryValue = primaryFormatter(getLastValue(metric?.data)); + const styles = reactcss( { default: { @@ -120,7 +121,6 @@ export class Metric extends Component { if (this.props.reversed) { className += ' tvbVisMetric--reversed'; } - return (
(this.resize = el)} className="tvbVisMetric__resize"> diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js index 2559ed543e543d..0c43ab157fbbba 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { getLastValue } from '../../../../common/get_last_value'; +import { getLastValue, isEmptyValue } from '../../../../common/last_value_utils'; import { labelDateFormatter } from '../../components/lib/label_date_formatter'; import { emptyLabel } from '../../../../common/empty_label'; import reactcss from 'reactcss'; @@ -97,15 +97,16 @@ export class TopN extends Component { const renderMode = TopN.getRenderMode(min, max); const key = `${item.id || item.label}`; const lastValue = getLastValue(item.data); + // if result is empty, all bar need to be colored. + const lastValueFormatted = isEmptyValue(lastValue) ? 1 : lastValue; const formatter = item.tickFormatter || this.props.tickFormatter; - const isPositiveValue = lastValue >= 0; + const isPositiveValue = lastValueFormatted >= 0; const intervalLength = TopN.calcDomain(renderMode, min, max); // if both are 0, the division returns NaN causing unexpected behavior. // For this it defaults to 0 - const width = 100 * (Math.abs(lastValue) / intervalLength) || 0; + const width = 100 * (Math.abs(lastValueFormatted) / intervalLength) || 0; const label = item.labelFormatted ? labelDateFormatter(item.labelFormatted) : item.label; - const styles = reactcss( { default: { @@ -150,7 +151,7 @@ export class TopN extends Component { const intervalSettings = this.props.series.reduce( (acc, series, index) => { - const value = getLastValue(series.data); + const value = getLastValue(series.data) ?? 1; return { min: !index || value < acc.min ? value : acc.min, diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index cb105d7b439ccf..5cdea62af95361 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -31,20 +31,22 @@ export async function getVisData( const indexPatternsService = await framework.getIndexPatternsService(requestContext); const esQueryConfig = await getEsQueryConfig(uiSettings); - const services: VisTypeTimeseriesRequestServices = { - esQueryConfig, - esShardTimeout, - indexPatternsService, - uiSettings, - searchStrategyRegistry: framework.searchStrategyRegistry, - cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService), - }; - const promises = request.body.panels.map((panel) => { - if (panel.type === PANEL_TYPES.TABLE) { - return getTableData(requestContext, request, panel, services); - } - return getSeriesData(requestContext, request, panel, services); + const services: VisTypeTimeseriesRequestServices = { + esQueryConfig, + esShardTimeout, + indexPatternsService, + uiSettings, + searchStrategyRegistry: framework.searchStrategyRegistry, + cachedIndexPatternFetcher: getCachedIndexPatternFetcher( + indexPatternsService, + Boolean(panel.use_kibana_indexes) + ), + }; + + return panel.type === PANEL_TYPES.TABLE + ? getTableData(requestContext, request, panel, services) + : getSeriesData(requestContext, request, panel, services); }); return Promise.all(promises).then((res) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts index b03fa973e9da99..26ea191ab92173 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts @@ -11,7 +11,10 @@ import { getIndexPatternKey, fetchIndexPattern } from '../../../../common/index_ import type { IndexPatternsService } from '../../../../../data/server'; import type { IndexPatternValue, FetchedIndexPattern } from '../../../../common/types'; -export const getCachedIndexPatternFetcher = (indexPatternsService: IndexPatternsService) => { +export const getCachedIndexPatternFetcher = ( + indexPatternsService: IndexPatternsService, + fetchKibanaIndexForStringIndexes: boolean = false +) => { const cache = new Map(); return async (indexPatternValue: IndexPatternValue): Promise => { @@ -21,7 +24,9 @@ export const getCachedIndexPatternFetcher = (indexPatternsService: IndexPatterns return cache.get(key); } - const fetchedIndex = fetchIndexPattern(indexPatternValue, indexPatternsService); + const fetchedIndex = fetchIndexPattern(indexPatternValue, indexPatternsService, { + fetchKibanaIndexForStringIndexes, + }); cache.set(key, fetchedIndex); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/math.js index fd7f5a06cac560..5abfc3e26ffcdd 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/math.js @@ -8,9 +8,9 @@ import { mathAgg } from '../series/math'; -export function math(bucket, panel, series) { +export function math(bucket, panel, series, meta, extractFields) { return (next) => (results) => { - const mathFn = mathAgg({ aggregations: bucket }, panel, series); + const mathFn = mathAgg({ aggregations: bucket }, panel, series, meta, extractFields); return mathFn(next)(results); }; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js index 9e3a2ac71ed025..88b06d7f7ffaa4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/table/process_bucket.js @@ -8,7 +8,7 @@ import { buildProcessorFunction } from '../build_processor_function'; import { processors } from '../response_processors/table'; -import { getLastValue } from '../../../../common/get_last_value'; +import { getLastValue } from '../../../../common/last_value_utils'; import { first, get } from 'lodash'; import { overwrite } from '../helpers'; import { getActiveSeries } from '../helpers/get_active_series'; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis.scss b/src/plugins/vis_type_vega/public/components/vega_vis.scss index f0062869e00469..5b96eb9a560c76 100644 --- a/src/plugins/vis_type_vega/public/components/vega_vis.scss +++ b/src/plugins/vis_type_vega/public/components/vega_vis.scss @@ -18,7 +18,7 @@ z-index: 0; flex: 1 1 100%; - display: block; + //display determined by js max-width: 100%; max-height: 100%; width: 100%; diff --git a/src/plugins/vis_type_vega/public/data_model/types.ts b/src/plugins/vis_type_vega/public/data_model/types.ts index 8590b51d3b5ffe..255bd9774f9df8 100644 --- a/src/plugins/vis_type_vega/public/data_model/types.ts +++ b/src/plugins/vis_type_vega/public/data_model/types.ts @@ -147,7 +147,7 @@ export interface Bool { bool?: Bool; must?: DslQuery[]; filter?: Filter[]; - should?: never[]; + should?: Filter[]; must_not?: Filter[]; } diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index b7f2b064cf9c22..54084c7476b6b5 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -83,9 +83,10 @@ export class VegaBaseView { return; } + const containerDisplay = this._parser.useResize ? 'flex' : 'block'; this._$container = $('
') // Force a height here because css is not loaded in mocha test - .css('height', '100%') + .css({ height: '100%', display: containerDisplay }) .appendTo(this._$parentEl); this._$controls = $( `
` diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index 5da5ffcc637c6c..dd88822f7f0f36 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -245,7 +245,7 @@ const VisComponent = (props: VisComponentProps) => { if (Object.keys(overwriteColors).includes(seriesName)) { return overwriteColors[seriesName]; } - const outputColor = palettesRegistry?.get(visParams.palette.name).getColor( + const outputColor = palettesRegistry?.get(visParams.palette.name).getCategoricalColor( [ { name: seriesName, diff --git a/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts b/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts index 6f214745e12912..212c033a65c263 100644 --- a/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts +++ b/src/plugins/visualizations/server/embeddable/visualize_embeddable_factory.ts @@ -13,6 +13,7 @@ import { commonAddSupportOfDualIndexSelectionModeInTSVB, commonHideTSVBLastValueIndicator, commonRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel, + commonAddEmptyValueColorRule, } from '../migrations/visualization_common_migrations'; const byValueAddSupportOfDualIndexSelectionModeInTSVB = (state: SerializableState) => { @@ -36,6 +37,13 @@ const byValueRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel = (state: Serial }; }; +const byValueAddEmptyValueColorRule = (state: SerializableState) => { + return { + ...state, + savedVis: commonAddEmptyValueColorRule(state.savedVis), + }; +}; + export const visualizeEmbeddableFactory = (): EmbeddableRegistryDefinition => { return { id: 'visualization', @@ -47,6 +55,7 @@ export const visualizeEmbeddableFactory = (): EmbeddableRegistryDefinition => { byValueHideTSVBLastValueIndicator, byValueRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel )(state), + '7.14.0': (state) => flow(byValueAddEmptyValueColorRule)(state), }, }; }; diff --git a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts index 3f09f19d9ac63a..13b8d8c4a0f982 100644 --- a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts +++ b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +import { get, last } from 'lodash'; +import uuid from 'uuid'; + export const commonAddSupportOfDualIndexSelectionModeInTSVB = (visState: any) => { if (visState && visState.type === 'metrics') { const { params } = visState; @@ -42,3 +45,49 @@ export const commonRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel = (visStat return visState; }; + +export const commonAddEmptyValueColorRule = (visState: any) => { + if (visState && visState.type === 'metrics') { + const params: any = get(visState, 'params') || {}; + + const getRuleWithComparingToZero = (rules: any[] = []) => { + const compareWithEqualMethods = ['gte', 'lte']; + return last( + rules.filter((rule) => compareWithEqualMethods.includes(rule.operator) && rule.value === 0) + ); + }; + + const convertRuleToEmpty = (rule: any = {}) => ({ + ...rule, + id: uuid.v4(), + operator: 'empty', + value: null, + }); + + const addEmptyRuleToListIfNecessary = (rules: any[]) => { + const rule = getRuleWithComparingToZero(rules); + + if (rule) { + return [...rules, convertRuleToEmpty(rule)]; + } + + return rules; + }; + + const colorRules = { + bar_color_rules: addEmptyRuleToListIfNecessary(params.bar_color_rules), + background_color_rules: addEmptyRuleToListIfNecessary(params.background_color_rules), + gauge_color_rules: addEmptyRuleToListIfNecessary(params.gauge_color_rules), + }; + + return { + ...visState, + params: { + ...params, + ...colorRules, + }, + }; + } + + return visState; +}; diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts index dbe5482c442b7c..36e1635ad4730e 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts @@ -2017,4 +2017,101 @@ describe('migration visualization', () => { expect(params.use_kibana_indexes).toBeFalsy(); }); }); + + describe('7.14.0 tsvb - add empty value rule to savedObjects with less and greater then zero rules', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.14.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + const rule1 = { value: 0, operator: 'lte', color: 'rgb(145, 112, 184)' }; + const rule2 = { value: 0, operator: 'gte', color: 'rgb(96, 146, 192)' }; + const rule3 = { value: 0, operator: 'gt', color: 'rgb(84, 179, 153)' }; + const rule4 = { value: 0, operator: 'lt', color: 'rgb(84, 179, 153)' }; + + const createTestDocWithType = (params: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: `{ + "type":"metrics", + "params": ${JSON.stringify(params)} + }`, + }, + }); + + const checkEmptyRuleIsAddedToArray = ( + rulesArrayProperty: string, + prevParams: any, + migratedParams: any, + rule: any + ) => { + expect(migratedParams).toHaveProperty(rulesArrayProperty); + expect(Array.isArray(migratedParams[rulesArrayProperty])).toBeTruthy(); + expect(migratedParams[rulesArrayProperty].length).toBe( + prevParams[rulesArrayProperty].length + 1 + ); + + const lastElementIndex = migratedParams[rulesArrayProperty].length - 1; + expect(migratedParams[rulesArrayProperty][lastElementIndex]).toHaveProperty('operator'); + expect(migratedParams[rulesArrayProperty][lastElementIndex].operator).toEqual('empty'); + expect(migratedParams[rulesArrayProperty][lastElementIndex].color).toEqual(rule.color); + }; + + const checkRuleIsNotAddedToArray = ( + rulesArrayProperty: string, + prevParams: any, + migratedParams: any, + rule: any + ) => { + expect(migratedParams).toHaveProperty(rulesArrayProperty); + expect(Array.isArray(migratedParams[rulesArrayProperty])).toBeTruthy(); + expect(migratedParams[rulesArrayProperty].length).toBe(prevParams[rulesArrayProperty].length); + // expects, that array contains one element... + expect(migratedParams[rulesArrayProperty][0].operator).toBe(rule.operator); + }; + + it('should add empty rule if operator = lte and value = 0', () => { + const params = { + bar_color_rules: [rule1], + background_color_rules: [rule1], + gauge_color_rules: [rule1], + }; + const migratedTestDoc = migrate(createTestDocWithType(params)); + const { params: migratedParams } = JSON.parse(migratedTestDoc.attributes.visState); + + checkEmptyRuleIsAddedToArray('bar_color_rules', params, migratedParams, rule1); + checkEmptyRuleIsAddedToArray('background_color_rules', params, migratedParams, rule1); + checkEmptyRuleIsAddedToArray('gauge_color_rules', params, migratedParams, rule1); + }); + + it('should add empty rule if operator = gte and value = 0', () => { + const params = { + bar_color_rules: [rule2], + background_color_rules: [rule2], + gauge_color_rules: [rule2], + }; + const migratedTestDoc = migrate(createTestDocWithType(params)); + const { params: migratedParams } = JSON.parse(migratedTestDoc.attributes.visState); + + checkEmptyRuleIsAddedToArray('bar_color_rules', params, migratedParams, rule2); + checkEmptyRuleIsAddedToArray('background_color_rules', params, migratedParams, rule2); + checkEmptyRuleIsAddedToArray('gauge_color_rules', params, migratedParams, rule2); + }); + + it('should not add empty rule if operator = gt or lt and value = any', () => { + const params = { + bar_color_rules: [rule3], + background_color_rules: [rule3], + gauge_color_rules: [rule4], + }; + const migratedTestDoc = migrate(createTestDocWithType(params)); + const { params: migratedParams } = JSON.parse(migratedTestDoc.attributes.visState); + + checkRuleIsNotAddedToArray('bar_color_rules', params, migratedParams, rule3); + checkRuleIsNotAddedToArray('background_color_rules', params, migratedParams, rule3); + checkRuleIsNotAddedToArray('gauge_color_rules', params, migratedParams, rule4); + }); + }); }); diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts index b9885588b6f760..c5050b4a6940b7 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts @@ -15,6 +15,7 @@ import { commonAddSupportOfDualIndexSelectionModeInTSVB, commonHideTSVBLastValueIndicator, commonRemoveDefaultIndexPatternAndTimeFieldFromTSVBModel, + commonAddEmptyValueColorRule, } from './visualization_common_migrations'; const migrateIndexPattern: SavedObjectMigrationFn = (doc) => { @@ -966,6 +967,29 @@ const removeDefaultIndexPatternAndTimeFieldFromTSVBModel: SavedObjectMigrationFn }; }; +const addEmptyValueColorRule: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + const newVisState = commonAddEmptyValueColorRule(visState); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(newVisState), + }, + }; + } + return doc; +}; + export const visualizationSavedObjectTypeMigrations = { /** * We need to have this migration twice, once with a version prior to 7.0.0 once with a version @@ -1012,4 +1036,5 @@ export const visualizationSavedObjectTypeMigrations = { hideTSVBLastValueIndicator, removeDefaultIndexPatternAndTimeFieldFromTSVBModel ), + '7.14.0': flow(addEmptyValueColorRule), }; diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts index f539d0f9e4544e..11fb74200d7ddf 100644 --- a/test/api_integration/apis/search/bsearch.ts +++ b/test/api_integration/apis/search/bsearch.ts @@ -8,15 +8,18 @@ import expect from '@kbn/expect'; import request from 'superagent'; +import { inflateResponse } from '../../../../src/plugins/bfetch/public/streaming'; import { FtrProviderContext } from '../../ftr_provider_context'; import { painlessErrReq } from './painless_err_req'; import { verifyErrorResponse } from './verify_error'; -function parseBfetchResponse(resp: request.Response): Array> { +function parseBfetchResponse(resp: request.Response, compressed: boolean = false) { return resp.text .trim() .split('\n') - .map((item) => JSON.parse(item)); + .map((item) => { + return JSON.parse(compressed ? inflateResponse(item) : item); + }); } export default function ({ getService }: FtrProviderContext) { @@ -26,29 +29,69 @@ export default function ({ getService }: FtrProviderContext) { describe('bsearch', () => { describe('post', () => { it('should return 200 a single response', async () => { - const resp = await supertest.post(`/internal/bsearch`).send({ - batch: [ - { - request: { - params: { - body: { - query: { - match_all: {}, + const resp = await supertest + .post(`/internal/bsearch`) + .set({ 'X-Chunk-Encoding': '' }) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, + }, }, }, }, + options: { + strategy: 'es', + }, }, - }, - ], - }); + ], + }); + + const jsonBody = parseBfetchResponse(resp); + + expect(resp.status).to.be(200); + expect(jsonBody[0].id).to.be(0); + expect(jsonBody[0].result.isPartial).to.be(false); + expect(jsonBody[0].result.isRunning).to.be(false); + expect(jsonBody[0].result).to.have.property('rawResponse'); + }); + + it('should return 200 a single response from compressed', async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set({ 'X-Chunk-Encoding': 'deflate' }) + .send({ + batch: [ + { + request: { + params: { + index: '.kibana', + body: { + query: { + match_all: {}, + }, + }, + }, + }, + options: { + strategy: 'es', + }, + }, + ], + }); - const jsonBody = JSON.parse(resp.text); + const jsonBody = parseBfetchResponse(resp, true); expect(resp.status).to.be(200); - expect(jsonBody.id).to.be(0); - expect(jsonBody.result).to.have.property('isPartial'); - expect(jsonBody.result).to.have.property('isRunning'); - expect(jsonBody.result).to.have.property('rawResponse'); + expect(jsonBody[0].id).to.be(0); + expect(jsonBody[0].result.isPartial).to.be(false); + expect(jsonBody[0].result.isRunning).to.be(false); + expect(jsonBody[0].result).to.have.property('rawResponse'); }); it('should return a batch of successful responses', async () => { @@ -57,6 +100,7 @@ export default function ({ getService }: FtrProviderContext) { { request: { params: { + index: '.kibana', body: { query: { match_all: {}, @@ -68,6 +112,7 @@ export default function ({ getService }: FtrProviderContext) { { request: { params: { + index: '.kibana', body: { query: { match_all: {}, @@ -95,6 +140,7 @@ export default function ({ getService }: FtrProviderContext) { { request: { params: { + index: '.kibana', body: { query: { match_all: {}, @@ -121,6 +167,7 @@ export default function ({ getService }: FtrProviderContext) { batch: [ { request: { + index: '.kibana', indexType: 'baad', params: { body: { diff --git a/test/api_integration/apis/ui_counters/ui_counters.ts b/test/api_integration/apis/ui_counters/ui_counters.ts index aa201eb6a96ff5..ab3ca2e8dd3a7b 100644 --- a/test/api_integration/apis/ui_counters/ui_counters.ts +++ b/test/api_integration/apis/ui_counters/ui_counters.ts @@ -47,7 +47,8 @@ export default function ({ getService }: FtrProviderContext) { return savedObject; }; - describe('UI Counters API', () => { + // FLAKY: https://github.com/elastic/kibana/issues/98240 + describe.skip('UI Counters API', () => { const dayDate = moment().format('DDMMYYYY'); before(async () => await esArchiver.emptyKibanaIndex()); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index df148a35aaf24e..6f298a364abfa7 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -10,6 +10,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); const log = getService('log'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); @@ -59,6 +60,46 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); }); + describe('classic table in window 900x700', async function () { + before(async () => { + await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); + await browser.setWindowSize(900, 700); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it(`should load up to ${rowsHardLimit} rows when scrolling at the end of the table with `, async function () { + const initialRows = await testSubjects.findAll('docTableRow'); + await testSubjects.scrollIntoView('discoverBackToTop'); + // now count the rows + await retry.waitFor('next batch of documents to be displayed', async () => { + const actual = await testSubjects.findAll('docTableRow'); + log.debug(`initial doc nr: ${initialRows.length}, actual doc nr: ${actual.length}`); + return actual.length > initialRows.length; + }); + }); + }); + + describe('classic table in window 600x700', async function () { + before(async () => { + await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); + await browser.setWindowSize(600, 700); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it(`should load up to ${rowsHardLimit} rows when scrolling at the end of the table with `, async function () { + const initialRows = await testSubjects.findAll('docTableRow'); + await testSubjects.scrollIntoView('discoverBackToTop'); + // now count the rows + await retry.waitFor('next batch of documents to be displayed', async () => { + const actual = await testSubjects.findAll('docTableRow'); + log.debug(`initial doc nr: ${initialRows.length}, actual doc nr: ${actual.length}`); + return actual.length > initialRows.length; + }); + }); + }); + describe('legacy', async function () { before(async () => { await kibanaServer.uiSettings.update({ 'doc_table:legacy': true }); diff --git a/x-pack/plugins/alerting/server/config.test.ts b/x-pack/plugins/alerting/server/config.test.ts index 069c41605ccfbc..a8befe5210752e 100644 --- a/x-pack/plugins/alerting/server/config.test.ts +++ b/x-pack/plugins/alerting/server/config.test.ts @@ -8,10 +8,11 @@ import { configSchema } from './config'; describe('config validation', () => { - test('alerts defaults', () => { + test('alerting defaults', () => { const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { + "enableImportExport": false, "healthCheck": Object { "interval": "60m", }, diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index e42955b385bf1e..d50917fd135785 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -16,6 +16,7 @@ export const configSchema = schema.object({ interval: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '1h' }), }), + enableImportExport: schema.boolean({ defaultValue: false }), }); export type AlertsConfig = TypeOf; diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts index 643d966d1fad0b..96627e10fb3bdf 100644 --- a/x-pack/plugins/alerting/server/health/get_state.test.ts +++ b/x-pack/plugins/alerting/server/health/get_state.test.ts @@ -72,6 +72,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }), pollInterval ).subscribe(); @@ -107,6 +108,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }), pollInterval, retryDelay @@ -152,6 +154,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }) ).toPromise(); @@ -182,6 +185,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }) ).toPromise(); @@ -212,6 +216,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }) ).toPromise(); @@ -239,6 +244,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }), retryDelay ).subscribe((status) => { @@ -269,6 +275,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }), retryDelay ).subscribe((status) => { @@ -305,6 +312,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }) ).toPromise(); diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index ec4b7095d67f7f..4e9249944a6bf9 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -18,6 +18,7 @@ import { AlertsConfig } from './config'; import { AlertType } from './types'; import { eventLogMock } from '../../event_log/server/mocks'; import { actionsMock } from '../../actions/server/mocks'; +import mappings from './saved_objects/mappings.json'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -25,6 +26,8 @@ describe('Alerting Plugin', () => { let coreSetup: ReturnType; let pluginsSetup: jest.Mocked; + beforeEach(() => jest.clearAllMocks()); + it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => { const context = coreMock.createPluginInitializerContext({ healthCheck: { @@ -34,6 +37,7 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }); plugin = new AlertingPlugin(context); @@ -57,6 +61,72 @@ describe('Alerting Plugin', () => { ); }); + it('should register saved object with no management capability if enableImportExport is false', async () => { + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + enableImportExport: false, + }); + plugin = new AlertingPlugin(context); + + const setupMocks = coreMock.createSetup(); + await plugin.setup(setupMocks, { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); + + expect(setupMocks.savedObjects.registerType).toHaveBeenCalledTimes(2); + const registerAlertingSavedObject = setupMocks.savedObjects.registerType.mock.calls[0][0]; + expect(registerAlertingSavedObject.name).toEqual('alert'); + expect(registerAlertingSavedObject.hidden).toBe(true); + expect(registerAlertingSavedObject.mappings).toEqual(mappings.alert); + expect(registerAlertingSavedObject.management).toBeUndefined(); + }); + + it('should register saved object with import/export capability if enableImportExport is true', async () => { + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + enableImportExport: true, + }); + plugin = new AlertingPlugin(context); + + const setupMocks = coreMock.createSetup(); + await plugin.setup(setupMocks, { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); + + expect(setupMocks.savedObjects.registerType).toHaveBeenCalledTimes(2); + const registerAlertingSavedObject = setupMocks.savedObjects.registerType.mock.calls[0][0]; + expect(registerAlertingSavedObject.name).toEqual('alert'); + expect(registerAlertingSavedObject.hidden).toBe(true); + expect(registerAlertingSavedObject.mappings).toEqual(mappings.alert); + expect(registerAlertingSavedObject.management).not.toBeUndefined(); + expect(registerAlertingSavedObject.management?.importableAndExportable).toBe(true); + expect(registerAlertingSavedObject.management?.getTitle).not.toBeUndefined(); + expect(registerAlertingSavedObject.management?.onImport).not.toBeUndefined(); + expect(registerAlertingSavedObject.management?.onExport).not.toBeUndefined(); + }); + describe('registerType()', () => { let setup: PluginSetupContract; const sampleAlertType: AlertType = { @@ -119,6 +189,7 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }); const plugin = new AlertingPlugin(context); @@ -158,6 +229,7 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }); const plugin = new AlertingPlugin(context); @@ -211,6 +283,7 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, + enableImportExport: false, }); const plugin = new AlertingPlugin(context); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 990733c320dfe8..769243b8feaf6a 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -190,7 +190,7 @@ export class AlertingPlugin { event: { provider: EVENT_LOG_PROVIDER }, }); - setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects); + setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects, this.config); this.eventLogService = plugins.eventLog; plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 6b76fd97dc53b7..c339183eeedcdb 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -16,6 +16,7 @@ import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objec import { transformRulesForExport } from './transform_rule_for_export'; import { RawAlert } from '../types'; import { getImportWarnings } from './get_import_warnings'; +import { AlertsConfig } from '../config'; export { partiallyUpdateAlert } from './partially_update_alert'; export const AlertAttributesExcludedFromAAD = [ @@ -41,59 +42,66 @@ export type AlertAttributesExcludedFromAADType = export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, - encryptedSavedObjects: EncryptedSavedObjectsPluginSetup + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + alertingConfig: Promise ) { - savedObjects.registerType({ - name: 'alert', - hidden: true, - namespaceType: 'single', - migrations: getMigrations(encryptedSavedObjects), - mappings: mappings.alert, - management: { - importableAndExportable: true, - getTitle(ruleSavedObject: SavedObject) { - return `Rule: [${ruleSavedObject.attributes.name}]`; - }, - onImport(ruleSavedObjects) { - return { - warnings: getImportWarnings(ruleSavedObjects), - }; - }, - onExport( - context: SavedObjectsExportTransformContext, - objects: Array> - ) { - return transformRulesForExport(objects); - }, - }, - }); + alertingConfig.then((config: AlertsConfig) => { + savedObjects.registerType({ + name: 'alert', + hidden: true, + namespaceType: 'single', + migrations: getMigrations(encryptedSavedObjects), + mappings: mappings.alert, + ...(config.enableImportExport + ? { + management: { + importableAndExportable: true, + getTitle(ruleSavedObject: SavedObject) { + return `Rule: [${ruleSavedObject.attributes.name}]`; + }, + onImport(ruleSavedObjects) { + return { + warnings: getImportWarnings(ruleSavedObjects), + }; + }, + onExport( + context: SavedObjectsExportTransformContext, + objects: Array> + ) { + return transformRulesForExport(objects); + }, + }, + } + : {}), + }); - savedObjects.registerType({ - name: 'api_key_pending_invalidation', - hidden: true, - namespaceType: 'agnostic', - mappings: { - properties: { - apiKeyId: { - type: 'keyword', - }, - createdAt: { - type: 'date', + savedObjects.registerType({ + name: 'api_key_pending_invalidation', + hidden: true, + namespaceType: 'agnostic', + mappings: { + properties: { + apiKeyId: { + type: 'keyword', + }, + createdAt: { + type: 'date', + }, }, }, - }, - }); + }); - // Encrypted attributes - encryptedSavedObjects.registerType({ - type: 'alert', - attributesToEncrypt: new Set(['apiKey']), - attributesToExcludeFromAAD: new Set(AlertAttributesExcludedFromAAD), - }); + // Encrypted attributes + encryptedSavedObjects.registerType({ + type: 'alert', + attributesToEncrypt: new Set(['apiKey']), + attributesToExcludeFromAAD: new Set(AlertAttributesExcludedFromAAD), + }); - // Encrypted attributes - encryptedSavedObjects.registerType({ - type: 'api_key_pending_invalidation', - attributesToEncrypt: new Set(['apiKeyId']), + // Encrypted attributes + encryptedSavedObjects.registerType({ + type: 'api_key_pending_invalidation', + attributesToEncrypt: new Set(['apiKeyId']), + }); }); } diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 120ab6de296dd8..78d74b78c99ba6 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -180,7 +180,8 @@ test('enqueues execution per selected action', async () => { `); expect(jest.requireMock('./inject_action_params').injectActionParams).toHaveBeenCalledWith({ - alertId: '1', + ruleId: '1', + spaceId: 'default', actionTypeId: 'test', actionParams: { alertVal: 'My 1 name-of-alert default tag-A,tag-B 2 goes here', diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 2ecf5404856954..93cced2043d5e1 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -138,7 +138,8 @@ export function createExecutionHandler< .map((action) => ({ ...action, params: injectActionParams({ - alertId, + ruleId: alertId, + spaceId, actionParams: action.params, actionTypeId: action.actionTypeId, }), diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts index 62d834eb91da0d..0416a3c4d1214a 100644 --- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts @@ -14,7 +14,8 @@ describe('injectActionParams', () => { }; const result = injectActionParams({ actionParams, - alertId: '1', + ruleId: '1', + spaceId: 'the-space', actionTypeId: '.server-log', }); expect(result).toMatchInlineSnapshot(` @@ -32,7 +33,8 @@ describe('injectActionParams', () => { }; const result = injectActionParams({ actionParams, - alertId: '1', + ruleId: '1', + spaceId: 'default', actionTypeId: '.email', }); expect(result).toMatchInlineSnapshot(` @@ -41,8 +43,58 @@ describe('injectActionParams', () => { "message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"", }, "kibanaFooterLink": Object { - "path": "/app/management/insightsAndAlerting/triggersActions/alert/1", - "text": "View alert in Kibana", + "path": "/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "View rule in Kibana", + }, + } + `); + }); + + test('injects viewInKibanaPath and viewInKibanaText when actionTypeId is .email and spaceId is undefined', () => { + const actionParams = { + body: { + message: 'State: "{{state.value}}", Context: "{{context.value}}"', + }, + }; + const result = injectActionParams({ + actionParams, + ruleId: '1', + spaceId: undefined, + actionTypeId: '.email', + }); + expect(result).toMatchInlineSnapshot(` + Object { + "body": Object { + "message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"", + }, + "kibanaFooterLink": Object { + "path": "/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "View rule in Kibana", + }, + } + `); + }); + + test('injects viewInKibanaPath with space ID and viewInKibanaText when actionTypeId is .email', () => { + const actionParams = { + body: { + message: 'State: "{{state.value}}", Context: "{{context.value}}"', + }, + }; + const result = injectActionParams({ + actionParams, + ruleId: '1', + spaceId: 'not-the-default', + actionTypeId: '.email', + }); + expect(result).toMatchInlineSnapshot(` + Object { + "body": Object { + "message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"", + }, + "kibanaFooterLink": Object { + "path": "/s/not-the-default/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "View rule in Kibana", }, } `); diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts index 177622867565cd..11ac3f92d1071b 100644 --- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts @@ -9,25 +9,29 @@ import { i18n } from '@kbn/i18n'; import { AlertActionParams } from '../types'; export interface InjectActionParamsOpts { - alertId: string; + ruleId: string; + spaceId: string | undefined; actionTypeId: string; actionParams: AlertActionParams; } export function injectActionParams({ - alertId, + ruleId, + spaceId, actionTypeId, actionParams, }: InjectActionParamsOpts) { // Inject kibanaFooterLink if action type is email. This is used by the email action type // to inject a "View alert in Kibana" with a URL in the email's footer. if (actionTypeId === '.email') { + const spacePrefix = + spaceId && spaceId.length > 0 && spaceId !== 'default' ? `/s/${spaceId}` : ''; return { ...actionParams, kibanaFooterLink: { - path: `/app/management/insightsAndAlerting/triggersActions/alert/${alertId}`, + path: `${spacePrefix}/app/management/insightsAndAlerting/triggersActions/rule/${ruleId}`, text: i18n.translate('xpack.alerting.injectActionParams.email.kibanaFooterLinkText', { - defaultMessage: 'View alert in Kibana', + defaultMessage: 'View rule in Kibana', }), }, }; diff --git a/x-pack/plugins/ingest_pipelines/server/services/index.ts b/x-pack/plugins/apm/common/index_pattern_constants.ts similarity index 77% rename from x-pack/plugins/ingest_pipelines/server/services/index.ts rename to x-pack/plugins/apm/common/index_pattern_constants.ts index b006d728052574..4b67bba1fef910 100644 --- a/x-pack/plugins/ingest_pipelines/server/services/index.ts +++ b/x-pack/plugins/apm/common/index_pattern_constants.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { License } from './license'; +export const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; diff --git a/src/plugins/apm_oss/public/assets/apm.png b/x-pack/plugins/apm/public/assets/apm.png similarity index 100% rename from src/plugins/apm_oss/public/assets/apm.png rename to x-pack/plugins/apm/public/assets/apm.png diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index 8954adf2c18c71..c998964b864003 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -22,7 +22,7 @@ import { SYMBOLIZE_AS_TYPES, } from '../../../../../../maps/common/constants'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts index 7a40880eb90536..e989577ac15aaa 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts @@ -17,7 +17,7 @@ import { USER_AGENT_OS, } from '../../../../../common/elasticsearch_fieldnames'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; const getWildcardFilter = (field: string, value: string): Filter => { return { diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 4343d504c33739..919d140c54c1eb 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import rison, { RisonValue } from 'rison-node'; import url from 'url'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index e627e9ed1d6cf6..607a7e6227a9d6 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -6,10 +6,8 @@ */ import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; -import { - apmIndexPattern, - APM_STATIC_INDEX_PATTERN_ID, -} from '../../../../../../src/plugins/apm_oss/server'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_constants'; +import apmIndexPattern from '../../tutorial/index_pattern.json'; import { hasHistoricalAgentData } from '../services/get_services/has_historical_agent_data'; import { Setup } from '../helpers/setup_request'; import { APMRouteHandlerResources } from '../../routes/typings'; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 8d83f762e2023e..824eba9bce0b08 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import { combineLatest } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { @@ -32,7 +31,6 @@ import { createApmAgentConfigurationIndex } from './lib/settings/agent_configura import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index'; import { apmIndices, apmTelemetry } from './saved_objects'; -import { createElasticCloudInstructions } from './tutorial/elastic_cloud'; import { uiSettings } from './ui_settings'; import type { ApmPluginRequestHandlerContext, @@ -51,6 +49,7 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../common/elasticsearch_fieldnames'; +import { tutorialProvider } from './tutorial'; export class APMPlugin implements @@ -103,28 +102,20 @@ export class APMPlugin }); } - const ossTutorialProvider = plugins.apmOss.getRegisteredTutorialProvider(); - plugins.home?.tutorials.unregisterTutorial(ossTutorialProvider); - plugins.home?.tutorials.registerTutorial(() => { - const ossPart = ossTutorialProvider({}); - if (this.currentConfig!['xpack.apm.ui.enabled'] && ossPart.artifacts) { - // @ts-expect-error ossPart.artifacts.application is readonly - ossPart.artifacts.application = { - path: '/app/apm', - label: i18n.translate( - 'xpack.apm.tutorial.specProvider.artifacts.application.label', - { - defaultMessage: 'Launch APM', - } - ), - }; - } - - return { - ...ossPart, - elasticCloud: createElasticCloudInstructions(plugins.cloud), - }; - }); + plugins.home?.tutorials.registerTutorial( + tutorialProvider({ + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + cloud: plugins.cloud, + indices: { + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'], + }, + }) + ); plugins.features.registerKibanaFeature(APM_FEATURE); diff --git a/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts similarity index 94% rename from x-pack/plugins/apm/server/tutorial/elastic_cloud.ts rename to x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts index 08e1ff75d43242..c6afd6a592fff2 100644 --- a/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../../../src/plugins/home/server'; +import { INSTRUCTION_VARIANT } from '../../../../../../src/plugins/home/server'; import { createNodeAgentInstructions, @@ -19,8 +19,8 @@ import { createJavaAgentInstructions, createDotNetAgentInstructions, createPhpAgentInstructions, -} from '../../../../../src/plugins/apm_oss/server'; -import { CloudSetup } from '../../../cloud/server'; +} from '../instructions/apm_agent_instructions'; +import { CloudSetup } from '../../../../cloud/server'; export function createElasticCloudInstructions(cloudSetup?: CloudSetup) { const apmServerUrl = cloudSetup?.apm.url; diff --git a/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts similarity index 52% rename from src/plugins/apm_oss/server/tutorial/envs/on_prem.ts rename to x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 7d261abb0cc018..a0e96f563381cf 100644 --- a/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -1,13 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../../home/server'; +import { INSTRUCTION_VARIANT } from '../../../../../../src/plugins/home/server'; import { createWindowsServerInstructions, createEditConfig, @@ -50,31 +49,46 @@ export function onPremInstructions({ return { instructionSets: [ { - title: i18n.translate('apmOss.tutorial.apmServer.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.title', { defaultMessage: 'APM Server', }), callOut: { - title: i18n.translate('apmOss.tutorial.apmServer.callOut.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.callOut.title', { defaultMessage: 'Important: Updating to 7.0 or higher', }), - message: i18n.translate('apmOss.tutorial.apmServer.callOut.message', { - defaultMessage: `Please make sure your APM Server is updated to 7.0 or higher. \ + message: i18n.translate( + 'xpack.apm.tutorial.apmServer.callOut.message', + { + defaultMessage: `Please make sure your APM Server is updated to 7.0 or higher. \ You can also migrate your 6.x data with the migration assistant found in Kibana's management section.`, - }), + } + ), iconType: 'alert', }, instructionVariants: [ { id: INSTRUCTION_VARIANT.OSX, - instructions: [createDownloadServerOsx(), EDIT_CONFIG, START_SERVER_UNIX], + instructions: [ + createDownloadServerOsx(), + EDIT_CONFIG, + START_SERVER_UNIX, + ], }, { id: INSTRUCTION_VARIANT.DEB, - instructions: [createDownloadServerDeb(), EDIT_CONFIG, START_SERVER_UNIX_SYSV], + instructions: [ + createDownloadServerDeb(), + EDIT_CONFIG, + START_SERVER_UNIX_SYSV, + ], }, { id: INSTRUCTION_VARIANT.RPM, - instructions: [createDownloadServerRpm(), EDIT_CONFIG, START_SERVER_UNIX_SYSV], + instructions: [ + createDownloadServerRpm(), + EDIT_CONFIG, + START_SERVER_UNIX_SYSV, + ], }, { id: INSTRUCTION_VARIANT.WINDOWS, @@ -82,23 +96,38 @@ export function onPremInstructions({ }, ], statusCheck: { - title: i18n.translate('apmOss.tutorial.apmServer.statusCheck.title', { - defaultMessage: 'APM Server status', - }), - text: i18n.translate('apmOss.tutorial.apmServer.statusCheck.text', { - defaultMessage: - 'Make sure APM Server is running before you start implementing the APM agents.', - }), - btnLabel: i18n.translate('apmOss.tutorial.apmServer.statusCheck.btnLabel', { - defaultMessage: 'Check APM Server status', - }), - success: i18n.translate('apmOss.tutorial.apmServer.statusCheck.successMessage', { - defaultMessage: 'You have correctly setup APM Server', - }), - error: i18n.translate('apmOss.tutorial.apmServer.statusCheck.errorMessage', { - defaultMessage: - 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', - }), + title: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.title', + { + defaultMessage: 'APM Server status', + } + ), + text: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.text', + { + defaultMessage: + 'Make sure APM Server is running before you start implementing the APM agents.', + } + ), + btnLabel: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.btnLabel', + { + defaultMessage: 'Check APM Server status', + } + ), + success: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.successMessage', + { + defaultMessage: 'You have correctly setup APM Server', + } + ), + error: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.errorMessage', + { + defaultMessage: + 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', + } + ), esHitsCheck: { index: onboardingIndices, query: { @@ -113,7 +142,7 @@ export function onPremInstructions({ }, }, { - title: i18n.translate('apmOss.tutorial.apmAgents.title', { + title: i18n.translate('xpack.apm.tutorial.apmAgents.title', { defaultMessage: 'APM Agents', }), instructionVariants: [ @@ -159,30 +188,56 @@ export function onPremInstructions({ }, ], statusCheck: { - title: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.title', { - defaultMessage: 'Agent status', - }), - text: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.text', { - defaultMessage: - 'Make sure your application is running and the agents are sending data.', - }), - btnLabel: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.btnLabel', { - defaultMessage: 'Check agent status', - }), - success: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.successMessage', { - defaultMessage: 'Data successfully received from one or more agents', - }), - error: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.errorMessage', { - defaultMessage: 'No data has been received from agents yet', - }), + title: i18n.translate( + 'xpack.apm.tutorial.apmAgents.statusCheck.title', + { + defaultMessage: 'Agent status', + } + ), + text: i18n.translate( + 'xpack.apm.tutorial.apmAgents.statusCheck.text', + { + defaultMessage: + 'Make sure your application is running and the agents are sending data.', + } + ), + btnLabel: i18n.translate( + 'xpack.apm.tutorial.apmAgents.statusCheck.btnLabel', + { + defaultMessage: 'Check agent status', + } + ), + success: i18n.translate( + 'xpack.apm.tutorial.apmAgents.statusCheck.successMessage', + { + defaultMessage: + 'Data successfully received from one or more agents', + } + ), + error: i18n.translate( + 'xpack.apm.tutorial.apmAgents.statusCheck.errorMessage', + { + defaultMessage: 'No data has been received from agents yet', + } + ), esHitsCheck: { - index: [errorIndices, transactionIndices, metricsIndices, sourcemapIndices], + index: [ + errorIndices, + transactionIndices, + metricsIndices, + sourcemapIndices, + ], query: { bool: { filter: [ { terms: { - 'processor.event': ['error', 'transaction', 'metric', 'sourcemap'], + 'processor.event': [ + 'error', + 'transaction', + 'metric', + 'sourcemap', + ], }, }, { range: { 'observer.version_major': { gte: 7 } } }, diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts new file mode 100644 index 00000000000000..d678677a4b7514 --- /dev/null +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { onPremInstructions } from './envs/on_prem'; +import { createElasticCloudInstructions } from './envs/elastic_cloud'; +import apmIndexPattern from './index_pattern.json'; +import { CloudSetup } from '../../../cloud/server'; +import { + ArtifactsSchema, + TutorialsCategory, +} from '../../../../../src/plugins/home/server'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../common/index_pattern_constants'; + +const apmIntro = i18n.translate('xpack.apm.tutorial.introduction', { + defaultMessage: + 'Collect in-depth performance metrics and errors from inside your applications.', +}); +const moduleName = 'apm'; + +export const tutorialProvider = ({ + isEnabled, + indexPatternTitle, + indices, + cloud, +}: { + isEnabled: boolean; + indexPatternTitle: string; + cloud?: CloudSetup; + indices: { + errorIndices: string; + transactionIndices: string; + metricsIndices: string; + sourcemapIndices: string; + onboardingIndices: string; + }; +}) => () => { + const savedObjects = [ + { + ...apmIndexPattern, + id: APM_STATIC_INDEX_PATTERN_ID, + attributes: { + ...apmIndexPattern.attributes, + title: indexPatternTitle, + }, + }, + ]; + + const artifacts: ArtifactsSchema = { + dashboards: [ + { + id: '8d3ed660-7828-11e7-8c47-65b845b5cfb3', + linkLabel: i18n.translate( + 'xpack.apm.tutorial.specProvider.artifacts.dashboards.linkLabel', + { + defaultMessage: 'APM dashboard', + } + ), + isOverview: true, + }, + ], + }; + + if (isEnabled) { + // @ts-expect-error artifacts.application is readonly + artifacts.application = { + path: '/app/apm', + label: i18n.translate( + 'xpack.apm.tutorial.specProvider.artifacts.application.label', + { + defaultMessage: 'Launch APM', + } + ), + }; + } + + return { + id: 'apm', + name: i18n.translate('xpack.apm.tutorial.specProvider.name', { + defaultMessage: 'APM', + }), + moduleName, + category: TutorialsCategory.OTHER, + shortDescription: apmIntro, + longDescription: i18n.translate( + 'xpack.apm.tutorial.specProvider.longDescription', + { + defaultMessage: + 'Application Performance Monitoring (APM) collects in-depth \ +performance metrics and errors from inside your application. \ +It allows you to monitor the performance of thousands of applications in real time. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: + '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', + }, + } + ), + euiIconType: 'apmApp', + artifacts, + onPrem: onPremInstructions(indices), + elasticCloud: createElasticCloudInstructions(cloud), + previewImagePath: '/plugins/apm/assets/apm.png', + savedObjects, + savedObjectsInstallMsg: i18n.translate( + 'xpack.apm.tutorial.specProvider.savedObjectsInstallMsg', + { + defaultMessage: + 'An APM index pattern is required for some features in the APM UI.', + } + ), + }; +}; diff --git a/src/plugins/apm_oss/server/tutorial/index_pattern.json b/x-pack/plugins/apm/server/tutorial/index_pattern.json similarity index 100% rename from src/plugins/apm_oss/server/tutorial/index_pattern.json rename to x-pack/plugins/apm/server/tutorial/index_pattern.json diff --git a/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts new file mode 100644 index 00000000000000..a25021fac5d006 --- /dev/null +++ b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts @@ -0,0 +1,931 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const createNodeAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.nodeClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.nodeClient.install.textPre', { + defaultMessage: + 'Install the APM agent for Node.js as a dependency to your application.', + }), + commands: ['npm install elastic-apm-node --save'], + }, + { + title: i18n.translate('xpack.apm.tutorial.nodeClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.nodeClient.configure.textPre', { + defaultMessage: + 'Agents are libraries that run inside of your application process. \ +APM services are created programmatically based on the `serviceName`. \ +This agent supports a variety of frameworks but can also be used with your custom stack.', + }), + commands: `// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', + { + defaultMessage: + 'Add this to the VERY top of the first file loaded in your app', + } + )} +var apm = require('elastic-apm-node').start({curlyOpen} + + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Override the service name from package.json', + } + )} + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', + } + )} + serviceName: '', + + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + secretToken: '${secretToken}', + + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + serverUrl: '${apmServerUrl}', + + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + environment: 'production' +{curlyClose})`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.textPost', + { + defaultMessage: + 'See [the documentation]({documentationLink}) for advanced usage, including how to use with \ +[Babel/ES Modules]({babelEsModulesLink}).', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/nodejs/current/index.html', + babelEsModulesLink: + '{config.docs.base_url}guide/en/apm/agent/nodejs/current/advanced-setup.html#es-modules', + }, + } + ), + }, +]; + +export const createDjangoAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.djangoClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.djangoClient.install.textPre', { + defaultMessage: 'Install the APM agent for Python as a dependency.', + }), + commands: ['$ pip install elastic-apm'], + }, + { + title: i18n.translate('xpack.apm.tutorial.djangoClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.textPre', + { + defaultMessage: + 'Agents are libraries that run inside of your application process. \ +APM services are created programmatically based on the `SERVICE_NAME`.', + } + ), + commands: `# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.addAgentComment', + { + defaultMessage: 'Add the agent to the installed apps', + } + )} +INSTALLED_APPS = ( + 'elasticapm.contrib.django', + # ... +) + +ELASTIC_APM = {curlyOpen} + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } + )} + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } + )} + 'SERVICE_NAME': '', + + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + 'SECRET_TOKEN': '${secretToken}', + + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + 'SERVER_URL': '${apmServerUrl}', + + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + 'ENVIRONMENT': 'production', +{curlyClose} + +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment', + { + defaultMessage: + 'To send performance metrics, add our tracing middleware:', + } + )} +MIDDLEWARE = ( + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... +)`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for advanced usage.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/python/current/django-support.html', + }, + } + ), + }, +]; + +export const createFlaskAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.flaskClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.flaskClient.install.textPre', { + defaultMessage: 'Install the APM agent for Python as a dependency.', + }), + commands: ['$ pip install elastic-apm[flask]'], + }, + { + title: i18n.translate('xpack.apm.tutorial.flaskClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.textPre', + { + defaultMessage: + 'Agents are libraries that run inside of your application process. \ +APM services are created programmatically based on the `SERVICE_NAME`.', + } + ), + commands: `# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'initialize using environment variables', + } + )} +from elasticapm.contrib.flask import ElasticAPM +app = Flask(__name__) +apm = ElasticAPM(app) + +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.configureElasticApmComment', + { + defaultMessage: + "or configure to use ELASTIC_APM in your application's settings", + } + )} +from elasticapm.contrib.flask import ElasticAPM +app.config['ELASTIC_APM'] = {curlyOpen} + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } + )} + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } + )} + 'SERVICE_NAME': '', + + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + 'SECRET_TOKEN': '${secretToken}', + + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + 'SERVER_URL': '${apmServerUrl}', + + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + 'ENVIRONMENT': 'production', +{curlyClose} + +apm = ElasticAPM(app)`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for advanced usage.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/python/current/flask-support.html', + }, + } + ), + }, +]; + +export const createRailsAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.railsClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.railsClient.install.textPre', { + defaultMessage: 'Add the agent to your Gemfile.', + }), + commands: [`gem 'elastic-apm'`], + }, + { + title: i18n.translate('xpack.apm.tutorial.railsClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.railsClient.configure.textPre', + { + defaultMessage: + 'APM is automatically started when your app boots. Configure the agent, by creating the config file {configFile}', + values: { configFile: '`config/elastic_apm.yml`' }, + } + ), + commands: `# config/elastic_apm.yml: + +# Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space +# Defaults to the name of your Rails app +service_name: 'my-service' + +# Use if APM Server requires a secret token +secret_token: '${secretToken}' + +# Set the custom APM Server URL (default: http://localhost:8200) +server_url: '${apmServerUrl || 'http://localhost:8200'}' + +# Set the service environment +environment: 'production'`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.railsClient.configure.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/ruby/current/index.html', + }, + } + ), + }, +]; + +export const createRackAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.rackClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.rackClient.install.textPre', { + defaultMessage: 'Add the agent to your Gemfile.', + }), + commands: [`gem 'elastic-apm'`], + }, + { + title: i18n.translate('xpack.apm.tutorial.rackClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.rackClient.configure.textPre', { + defaultMessage: + 'For Rack or a compatible framework (e.g. Sinatra), include the middleware in your app and start the agent.', + }), + commands: `# config.ru + require 'sinatra/base' + + class MySinatraApp < Sinatra::Base + use ElasticAPM::Middleware + + # ... + end + + ElasticAPM.start( + app: MySinatraApp, # ${i18n.translate( + 'xpack.apm.tutorial.rackClient.configure.commands.requiredComment', + { + defaultMessage: 'required', + } + )} + config_file: '' # ${i18n.translate( + 'xpack.apm.tutorial.rackClient.configure.commands.optionalComment', + { + defaultMessage: 'optional, defaults to config/elastic_apm.yml', + } + )} + ) + + run MySinatraApp + + at_exit {curlyOpen} ElasticAPM.stop {curlyClose}`.split('\n'), + }, + { + title: i18n.translate('xpack.apm.tutorial.rackClient.createConfig.title', { + defaultMessage: 'Create config file', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.textPre', + { + defaultMessage: 'Create a config file {configFile}:', + values: { configFile: '`config/elastic_apm.yml`' }, + } + ), + commands: `# config/elastic_apm.yml: + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceNameComment', + { + defaultMessage: + 'Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', + } + )} +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', + { + defaultMessage: "Defaults to the name of your Rack app's class.", + } + )} +service_name: 'my-service' + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a token', + } + )} +secret_token: '${secretToken}' + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setCustomApmServerComment', + { + defaultMessage: + 'Set custom APM Server URL (default: {defaultServerUrl})', + values: { defaultServerUrl: 'http://localhost:8200' }, + } + )} +server_url: '${apmServerUrl || 'http://localhost:8200'}', + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceEnvironment', + { + defaultMessage: 'Set the service environment', + } + )} +environment: 'production'`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/ruby/current/index.html', + }, + } + ), + }, +]; + +export const createJsAgentInstructions = (apmServerUrl = '') => [ + { + title: i18n.translate( + 'xpack.apm.tutorial.jsClient.enableRealUserMonitoring.title', + { + defaultMessage: 'Enable Real User Monitoring support in APM Server', + } + ), + textPre: i18n.translate( + 'xpack.apm.tutorial.jsClient.enableRealUserMonitoring.textPre', + { + defaultMessage: + 'APM Server disables RUM support by default. See the [documentation]({documentationLink}) \ +for details on how to enable RUM support.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/server/{config.docs.version}/configuration-rum.html', + }, + } + ), + }, + { + title: i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.title', + { + defaultMessage: 'Set up the Agent as a dependency', + } + ), + textPre: i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.textPre', + { + defaultMessage: + 'You can install the Agent as a dependency to your application with \ +`npm install @elastic/apm-rum --save`.\n\n\ +The Agent can then be initialized and configured in your application like this:', + } + ), + commands: `import {curlyOpen} init as initApm {curlyClose} from '@elastic/apm-rum' +var apm = initApm({curlyOpen} + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment', + { + defaultMessage: + 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)', + } + )} + serviceName: 'your-app-name', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + serverUrl: '${apmServerUrl}', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceVersionComment', + { + defaultMessage: + 'Set the service version (required for source map feature)', + } + )} + serviceVersion: '', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + environment: 'production' +{curlyClose})`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.textPost', + { + defaultMessage: + 'Framework integrations, like React or Angular, have custom dependencies. \ +See the [integration documentation]({docLink}) for more information.', + values: { + docLink: + '{config.docs.base_url}guide/en/apm/agent/rum-js/current/framework-integrations.html', + }, + } + ), + }, + { + title: i18n.translate('xpack.apm.tutorial.jsClient.scriptTags.title', { + defaultMessage: 'Set up the Agent with Script Tags', + }), + textPre: i18n.translate('xpack.apm.tutorial.jsClient.scriptTags.textPre', { + defaultMessage: + "Alternatively, you can use Script tags to set up and configure the Agent. \ +Add a ` + +`.split('\n'), + }, +]; + +export const createGoAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.goClient.install.title', { + defaultMessage: 'Install the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.goClient.install.textPre', { + defaultMessage: 'Install the APM agent packages for Go.', + }), + commands: ['go get go.elastic.co/apm'], + }, + { + title: i18n.translate('xpack.apm.tutorial.goClient.configure.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.goClient.configure.textPre', { + defaultMessage: + 'Agents are libraries that run inside of your application process. \ +APM services are created programmatically based on the executable \ +file name, or the `ELASTIC_APM_SERVICE_NAME` environment variable.', + }), + commands: `# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'Initialize using environment variables:', + } + )} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setServiceNameComment', + { + defaultMessage: + 'Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.', + } + )} +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.usedExecutableNameComment', + { + defaultMessage: + 'If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.', + } + )} +export ELASTIC_APM_SERVICE_NAME= + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} +export ELASTIC_APM_SERVER_URL=${apmServerUrl} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} +export ELASTIC_APM_SECRET_TOKEN=${secretToken} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setServiceEnvironment', + { + defaultMessage: 'Set the service environment', + } + )} +export ELASTIC_APM_ENVIRONMENT= +`.split('\n'), + textPost: i18n.translate('xpack.apm.tutorial.goClient.configure.textPost', { + defaultMessage: + 'See the [documentation]({documentationLink}) for advanced configuration.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/go/current/configuration.html', + }, + }), + }, + { + title: i18n.translate('xpack.apm.tutorial.goClient.instrument.title', { + defaultMessage: 'Instrument your application', + }), + textPre: i18n.translate('xpack.apm.tutorial.goClient.instrument.textPre', { + defaultMessage: + 'Instrument your Go application by using one of the provided instrumentation modules or \ +by using the tracer API directly.', + }), + commands: `\ +import ( + "net/http" + + "go.elastic.co/apm/module/apmhttp" +) + +func main() {curlyOpen} + mux := http.NewServeMux() + ... + http.ListenAndServe(":8080", apmhttp.Wrap(mux)) +{curlyClose} +`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.goClient.instrument.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for a detailed \ +guide to instrumenting Go source code.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/go/current/instrumenting-source.html', + }, + } + ), + }, +]; + +export const createJavaAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.javaClient.download.title', { + defaultMessage: 'Download the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.javaClient.download.textPre', { + defaultMessage: + 'Download the agent jar from [Maven Central]({mavenCentralLink}). \ +Do **not** add the agent as a dependency to your application.', + values: { + mavenCentralLink: + 'http://search.maven.org/#search%7Cga%7C1%7Ca%3Aelastic-apm-agent', + }, + }), + }, + { + title: i18n.translate( + 'xpack.apm.tutorial.javaClient.startApplication.title', + { + defaultMessage: 'Start your application with the javaagent flag', + } + ), + textPre: i18n.translate( + 'xpack.apm.tutorial.javaClient.startApplication.textPre', + { + defaultMessage: + 'Add the `-javaagent` flag and configure the agent with system properties.\n\n \ +* Set the required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)\n \ +* Set the custom APM Server URL (default: {customApmServerUrl})\n \ +* Set the APM Server secret token\n \ +* Set the service environment\n \ +* Set the base package of your application', + values: { customApmServerUrl: 'http://localhost:8200' }, + } + ), + commands: `java -javaagent:/path/to/elastic-apm-agent-.jar \\ + -Delastic.apm.service_name=my-application \\ + -Delastic.apm.server_urls=${apmServerUrl || 'http://localhost:8200'} \\ + -Delastic.apm.secret_token=${secretToken} \\ + -Delastic.apm.environment=production \\ + -Delastic.apm.application_packages=org.example \\ + -jar my-application.jar`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.javaClient.startApplication.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for configuration options and advanced \ +usage.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/java/current/index.html', + }, + } + ), + }, +]; + +export const createDotNetAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.dotNetClient.download.title', { + defaultMessage: 'Download the APM agent', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.download.textPre', + { + defaultMessage: + 'Add the the agent package(s) from [NuGet]({allNuGetPackagesLink}) to your .NET application. There are multiple \ + NuGet packages available for different use cases. \n\nFor an ASP.NET Core application with Entity Framework \ + Core download the [Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}) package. This package will automatically add every \ + agent component to your application. \n\n In case you would like to minimize the dependencies, you can use the \ + [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) package for just \ + ASP.NET Core monitoring or the [Elastic.Apm.EfCore]({efCorePackageLink}) package for just Entity Framework Core monitoring. \n\n \ + In case you only want to use the public Agent API for manual instrumentation use the [Elastic.Apm]({elasticApmPackageLink}) package.', + values: { + allNuGetPackagesLink: 'https://www.nuget.org/packages?q=Elastic.apm', + netCoreAllApmPackageLink: + 'https://www.nuget.org/packages/Elastic.Apm.NetCoreAll', + aspNetCorePackageLink: + 'https://www.nuget.org/packages/Elastic.Apm.AspNetCore', + efCorePackageLink: + 'https://www.nuget.org/packages/Elastic.Apm.EntityFrameworkCore', + elasticApmPackageLink: 'https://www.nuget.org/packages/Elastic.Apm', + }, + } + ), + }, + { + title: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.configureApplication.title', + { + defaultMessage: 'Add the agent to the application', + } + ), + textPre: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.configureApplication.textPre', + { + defaultMessage: + 'In case of ASP.NET Core with the `Elastic.Apm.NetCoreAll` package, call the `UseAllElasticApm` \ + method in the `Configure` method within the `Startup.cs` file.', + } + ), + commands: `public class Startup +{curlyOpen} + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + {curlyOpen} + app.UseAllElasticApm(Configuration); + //…rest of the method + {curlyClose} + //…rest of the class +{curlyClose}`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.configureApplication.textPost', + { + defaultMessage: + 'Passing an `IConfiguration` instance is optional and by doing so, the agent will read config settings through this \ + `IConfiguration` instance (e.g. from the `appsettings.json` file).', + } + ), + }, + { + title: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.configureAgent.title', + { + defaultMessage: 'Sample appsettings.json file:', + } + ), + commands: `{curlyOpen} + "ElasticApm": {curlyOpen} + "SecretToken": "${secretToken}", + "ServerUrls": "${ + apmServerUrl || 'http://localhost:8200' + }", //Set custom APM Server URL (default: http://localhost:8200) + "ServiceName": "MyApp", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application + "Environment": "production", // Set the service environment + {curlyClose} +{curlyClose}`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.dotNetClient.configureAgent.textPost', + { + defaultMessage: + 'In case you don’t pass an `IConfiguration` instance to the agent (e.g. in case of non ASP.NET Core applications) \ + you can also configure the agent through environment variables. \n \ + See [the documentation]({documentationLink}) for advanced usage.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/dotnet/current/configuration.html', + }, + } + ), + }, +]; + +export const createPhpAgentInstructions = ( + apmServerUrl = '', + secretToken = '' +) => [ + { + title: i18n.translate('xpack.apm.tutorial.phpClient.download.title', { + defaultMessage: 'Download the APM agent', + }), + textPre: i18n.translate('xpack.apm.tutorial.phpClient.download.textPre', { + defaultMessage: + 'Download the package corresponding to your platform from [GitHub releases]({githubReleasesLink}).', + values: { + githubReleasesLink: 'https://github.com/elastic/apm-agent-php/releases', + }, + }), + }, + { + title: i18n.translate('xpack.apm.tutorial.phpClient.installPackage.title', { + defaultMessage: 'Install the downloaded package', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.phpClient.installPackage.textPre', + { + defaultMessage: 'For example on Alpine Linux using APK package:', + } + ), + commands: ['apk add --allow-untrusted .apk'], + textPost: i18n.translate( + 'xpack.apm.tutorial.phpClient.installPackage.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for installation commands on other supported platforms and advanced installation.', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/php/current/setup.html', + }, + } + ), + }, + { + title: i18n.translate('xpack.apm.tutorial.phpClient.configureAgent.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate( + 'xpack.apm.tutorial.phpClient.configureAgent.textPre', + { + defaultMessage: + 'APM is automatically started when your app boots. Configure the agent either via `php.ini` file:', + } + ), + commands: `elastic_apm.server_url=http://localhost:8200 +elastic_apm.service_name="My service" +`.split('\n'), + textPost: i18n.translate( + 'xpack.apm.tutorial.phpClient.configure.textPost', + { + defaultMessage: + 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/php/current/configuration.html', + }, + } + ), + }, +]; diff --git a/src/plugins/apm_oss/server/tutorial/instructions/apm_server_instructions.ts b/x-pack/plugins/apm/server/tutorial/instructions/apm_server_instructions.ts similarity index 66% rename from src/plugins/apm_oss/server/tutorial/instructions/apm_server_instructions.ts rename to x-pack/plugins/apm/server/tutorial/instructions/apm_server_instructions.ts index eee93d8dc9fd1a..c2fc7b1774f65f 100644 --- a/src/plugins/apm_oss/server/tutorial/instructions/apm_server_instructions.ts +++ b/x-pack/plugins/apm/server/tutorial/instructions/apm_server_instructions.ts @@ -1,18 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { i18n } from '@kbn/i18n'; export const createEditConfig = () => ({ - title: i18n.translate('apmOss.tutorial.editConfig.title', { + title: i18n.translate('xpack.apm.tutorial.editConfig.title', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('apmOss.tutorial.editConfig.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.editConfig.textPre', { defaultMessage: "If you're using an X-Pack secured version of Elastic Stack, you must specify \ credentials in the `apm-server.yml` config file.", @@ -26,10 +25,10 @@ credentials in the `apm-server.yml` config file.", }); const createStartServer = () => ({ - title: i18n.translate('apmOss.tutorial.startServer.title', { + title: i18n.translate('xpack.apm.tutorial.startServer.title', { defaultMessage: 'Start APM Server', }), - textPre: i18n.translate('apmOss.tutorial.startServer.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.startServer.textPre', { defaultMessage: 'The server processes and stores application performance metrics in Elasticsearch.', }), @@ -56,7 +55,7 @@ export function createStartServerUnix() { } const createDownloadServerTitle = () => - i18n.translate('apmOss.tutorial.downloadServer.title', { + i18n.translate('xpack.apm.tutorial.downloadServer.title', { defaultMessage: 'Download and unpack APM Server', }); @@ -75,8 +74,9 @@ export const createDownloadServerDeb = () => ({ 'curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-{config.kibana.version}-amd64.deb', 'sudo dpkg -i apm-server-{config.kibana.version}-amd64.deb', ], - textPost: i18n.translate('apmOss.tutorial.downloadServerTitle', { - defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).', + textPost: i18n.translate('xpack.apm.tutorial.downloadServerTitle', { + defaultMessage: + 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).', values: { downloadPageLink: '{config.docs.base_url}downloads/apm/apm-server', }, @@ -89,8 +89,9 @@ export const createDownloadServerRpm = () => ({ 'curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-{config.kibana.version}-x86_64.rpm', 'sudo rpm -vi apm-server-{config.kibana.version}-x86_64.rpm', ], - textPost: i18n.translate('apmOss.tutorial.downloadServerRpm', { - defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).', + textPost: i18n.translate('xpack.apm.tutorial.downloadServerRpm', { + defaultMessage: + 'Looking for the 32-bit packages? See the [Download page]({downloadPageLink}).', values: { downloadPageLink: '{config.docs.base_url}downloads/apm/apm-server', }, @@ -103,32 +104,41 @@ export function createWindowsServerInstructions() { return [ { title: createDownloadServerTitle(), - textPre: i18n.translate('apmOss.tutorial.windowsServerInstructions.textPre', { - defaultMessage: - '1. Download the APM Server Windows zip file from the \ + textPre: i18n.translate( + 'xpack.apm.tutorial.windowsServerInstructions.textPre', + { + defaultMessage: + '1. Download the APM Server Windows zip file from the \ [Download page]({downloadPageLink}).\n2. Extract the contents of \ the zip file into {zipFileExtractFolder}.\n3. Rename the {apmServerDirectory} \ directory to `APM-Server`.\n4. Open a PowerShell prompt as an Administrator \ (right-click the PowerShell icon and select \ **Run As Administrator**). If you are running Windows XP, you might need to download and install \ PowerShell.\n5. From the PowerShell prompt, run the following commands to install APM Server as a Windows service:', - values: { - downloadPageLink: 'https://www.elastic.co/downloads/apm/apm-server', - zipFileExtractFolder: '`C:\\Program Files`', - apmServerDirectory: '`apm-server-{config.kibana.version}-windows`', - }, - }), - commands: [`cd 'C:\\Program Files\\APM-Server'`, `.\\install-service-apm-server.ps1`], - textPost: i18n.translate('apmOss.tutorial.windowsServerInstructions.textPost', { - defaultMessage: - 'Note: If script execution is disabled on your system, \ + values: { + downloadPageLink: 'https://www.elastic.co/downloads/apm/apm-server', + zipFileExtractFolder: '`C:\\Program Files`', + apmServerDirectory: '`apm-server-{config.kibana.version}-windows`', + }, + } + ), + commands: [ + `cd 'C:\\Program Files\\APM-Server'`, + `.\\install-service-apm-server.ps1`, + ], + textPost: i18n.translate( + 'xpack.apm.tutorial.windowsServerInstructions.textPost', + { + defaultMessage: + 'Note: If script execution is disabled on your system, \ you need to set the execution policy for the current session \ to allow the script to run. For example: {command}.', - values: { - command: - '`PowerShell.exe -ExecutionPolicy UnRestricted -File .\\install-service-apm-server.ps1`', - }, - }), + values: { + command: + '`PowerShell.exe -ExecutionPolicy UnRestricted -File .\\install-service-apm-server.ps1`', + }, + } + ), }, createEditConfig(), { diff --git a/x-pack/plugins/canvas/public/functions/pie.test.js b/x-pack/plugins/canvas/public/functions/pie.test.js index 915d8525079dbf..b1c17463408928 100644 --- a/x-pack/plugins/canvas/public/functions/pie.test.js +++ b/x-pack/plugins/canvas/public/functions/pie.test.js @@ -18,7 +18,7 @@ describe('pie', () => { const fn = functionWrapper( pieFunctionFactory({ get: () => ({ - getColors: () => ['red', 'black'], + getCategoricalColors: () => ['red', 'black'], }), }) ); @@ -59,7 +59,7 @@ describe('pie', () => { const mockedFn = functionWrapper( pieFunctionFactory({ get: () => ({ - getColors: mockedColors, + getCategoricalColors: mockedColors, }), }) ); diff --git a/x-pack/plugins/canvas/public/functions/pie.ts b/x-pack/plugins/canvas/public/functions/pie.ts index 0840667302ebef..a91dc16b770c9f 100644 --- a/x-pack/plugins/canvas/public/functions/pie.ts +++ b/x-pack/plugins/canvas/public/functions/pie.ts @@ -173,7 +173,7 @@ export function pieFunctionFactory( canvas: false, colors: paletteService .get(palette.name || 'custom') - .getColors(data.length, palette.params), + .getCategoricalColors(data.length, palette.params), legend: getLegendConfig(legend, data.length), grid: { show: false, diff --git a/x-pack/plugins/canvas/public/functions/plot.test.js b/x-pack/plugins/canvas/public/functions/plot.test.js index 849752d2c984ba..5ed858961d7980 100644 --- a/x-pack/plugins/canvas/public/functions/plot.test.js +++ b/x-pack/plugins/canvas/public/functions/plot.test.js @@ -21,7 +21,7 @@ describe('plot', () => { const fn = functionWrapper( plotFunctionFactory({ get: () => ({ - getColors: () => ['red', 'black'], + getCategoricalColors: () => ['red', 'black'], }), }) ); @@ -121,7 +121,7 @@ describe('plot', () => { const mockedFn = functionWrapper( plotFunctionFactory({ get: () => ({ - getColors: mockedColors, + getCategoricalColors: mockedColors, }), }) ); diff --git a/x-pack/plugins/canvas/public/functions/plot/index.ts b/x-pack/plugins/canvas/public/functions/plot/index.ts index c0c73c3a21bc6c..477c7041901461 100644 --- a/x-pack/plugins/canvas/public/functions/plot/index.ts +++ b/x-pack/plugins/canvas/public/functions/plot/index.ts @@ -144,7 +144,7 @@ export function plotFunctionFactory( canvas: false, colors: paletteService .get(args.palette.name || 'custom') - .getColors(data.length, args.palette.params), + .getCategoricalColors(data.length, args.palette.params), legend: getLegendConfig(args.legend, data.length), grid: gridConfig, xaxis: getFlotAxisConfig('x', args.xaxis, { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index de13077cd1b099..66f2bf78e0c9c3 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -12,9 +12,16 @@ import fetch from 'node-fetch'; const { Response } = jest.requireActual('node-fetch'); +jest.mock('@kbn/utils', () => ({ + kibanaPackageJson: { version: '1.0.0' }, +})); + import { loggingSystemMock } from 'src/core/server/mocks'; -import { callEnterpriseSearchConfigAPI } from './enterprise_search_config_api'; +import { + callEnterpriseSearchConfigAPI, + warnMismatchedVersions, +} from './enterprise_search_config_api'; describe('callEnterpriseSearchConfigAPI', () => { const mockConfig = { @@ -218,4 +225,22 @@ describe('callEnterpriseSearchConfigAPI', () => { "Exceeded 200ms timeout while checking http://localhost:3002. Please consider increasing your enterpriseSearch.accessCheckTimeout value so that users aren't prevented from accessing Enterprise Search plugins due to slow responses." ); }); + + describe('warnMismatchedVersions', () => { + it("logs a warning when Enterprise Search and Kibana's versions are not the same", () => { + warnMismatchedVersions('1.1.0', mockDependencies.log); + + expect(mockDependencies.log.warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Your Kibana instance (v1.0.0) is not the same version as your Enterprise Search instance (v1.1.0)' + ) + ); + }); + + it("does not log a warning when Enterprise Search and Kibana's versions are the same", () => { + warnMismatchedVersions('1.0.0', mockDependencies.log); + + expect(mockDependencies.log.warn).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index ebe718dfebd309..0f2faf1fd8a3ab 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -8,6 +8,8 @@ import AbortController from 'abort-controller'; import fetch from 'node-fetch'; +import { kibanaPackageJson } from '@kbn/utils'; + import { KibanaRequest, Logger } from 'src/core/server'; import { stripTrailingSlash } from '../../common/strip_slashes'; @@ -58,6 +60,8 @@ export const callEnterpriseSearchConfigAPI = async ({ }); const data = await response.json(); + warnMismatchedVersions(data?.version?.number, log); + return { access: { hasAppSearchAccess: !!data?.current_user?.access?.app_search, @@ -135,3 +139,13 @@ export const callEnterpriseSearchConfigAPI = async ({ clearTimeout(timeout); } }; + +export const warnMismatchedVersions = (enterpriseSearchVersion: string, log: Logger) => { + const kibanaVersion = kibanaPackageJson.version; + + if (enterpriseSearchVersion !== kibanaVersion) { + log.warn( + `Your Kibana instance (v${kibanaVersion}) is not the same version as your Enterprise Search instance (v${enterpriseSearchVersion}), which may cause unexpected behavior. Use matching versions for the best experience.` + ); + } +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.test.tsx new file mode 100644 index 00000000000000..27bf5af72fb61d --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, act } from '@testing-library/react'; + +import { createTestRendererMock } from '../../mock'; + +import { HostsInput } from './hosts_input'; + +function renderInput(value = ['http://host1.com']) { + const renderer = createTestRendererMock(); + const mockOnChange = jest.fn(); + + const utils = renderer.render( + + ); + + return { utils, mockOnChange }; +} + +test('it should allow to add a new host', async () => { + const { utils, mockOnChange } = renderInput(); + + const addRowEl = await utils.findByText('Add row'); + fireEvent.click(addRowEl); + expect(mockOnChange).toHaveBeenCalledWith(['http://host1.com', '']); +}); + +test('it should allow to remove an host', async () => { + const { utils, mockOnChange } = renderInput(['http://host1.com', 'http://host2.com']); + + await act(async () => { + const deleteRowEl = await utils.container.querySelector('[aria-label="Delete host"]'); + if (!deleteRowEl) { + throw new Error('Delete host button not found'); + } + fireEvent.click(deleteRowEl); + }); + + expect(mockOnChange).toHaveBeenCalledWith(['http://host2.com']); +}); + +test('it should allow to update existing host with single host', async () => { + const { utils, mockOnChange } = renderInput(['http://host1.com']); + + const inputEl = await utils.findByDisplayValue('http://host1.com'); + fireEvent.change(inputEl, { target: { value: 'http://newhost.com' } }); + expect(mockOnChange).toHaveBeenCalledWith(['http://newhost.com']); +}); + +test('it should allow to update existing host with multiple hosts', async () => { + const { utils, mockOnChange } = renderInput(['http://host1.com', 'http://host2.com']); + + const inputEl = await utils.findByDisplayValue('http://host1.com'); + fireEvent.change(inputEl, { target: { value: 'http://newhost.com' } }); + expect(mockOnChange).toHaveBeenCalledWith(['http://newhost.com', 'http://host2.com']); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.tsx new file mode 100644 index 00000000000000..0e5f9a5e028b5e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/hosts_input.tsx @@ -0,0 +1,247 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo, useCallback, useState } from 'react'; +import type { ReactNode, FunctionComponent, ChangeEvent } from 'react'; +import sytled, { useTheme } from 'styled-components'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiFormRow, + EuiFieldText, + EuiDragDropContext, + EuiDroppable, + EuiDraggable, + EuiIcon, + EuiButtonIcon, + EuiSpacer, + EuiFormHelpText, + euiDragDropReorder, + EuiFormErrorText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import type { EuiTheme } from '../../../../../../../../src/plugins/kibana_react/common'; + +interface Props { + id: string; + value: string[]; + onChange: (newValue: string[]) => void; + label: string; + helpText: ReactNode; + errors?: Array<{ message: string; index?: number }>; + isInvalid?: boolean; +} + +interface SortableTextFieldProps { + id: string; + index: number; + value: string; + onChange: (e: ChangeEvent) => void; + onDelete: (index: number) => void; + errors?: string[]; + autoFocus?: boolean; +} + +const DraggableDiv = sytled.div` + margin: ${(props) => props.theme.eui.euiSizeS}; +`; + +function displayErrors(errors?: string[]) { + return errors?.length + ? errors.map((error, errorIndex) => ( + {error} + )) + : null; +} + +const SortableTextField: FunctionComponent = React.memo( + ({ id, index, value, onChange, onDelete, autoFocus, errors }) => { + const onDeleteHandler = useCallback(() => { + onDelete(index); + }, [onDelete, index]); + + const isInvalid = (errors?.length ?? 0) > 0; + const theme = useTheme() as EuiTheme; + + return ( + + {(provided, state) => ( + + + + + + + + + {displayErrors(errors)} + + + + + + )} + + ); + } +); + +export const HostsInput: FunctionComponent = ({ + id, + value, + onChange, + helpText, + label, + isInvalid, + errors, +}) => { + const [autoFocus, setAutoFocus] = useState(false); + const rows = useMemo( + () => + value.map((host, idx) => ({ + value: host, + onChange: (e: ChangeEvent) => { + const newValue = [...value]; + newValue[idx] = e.target.value; + + onChange(newValue); + }, + })), + [value, onChange] + ); + + const onDelete = useCallback( + (idx: number) => { + onChange([...value.slice(0, idx), ...value.slice(idx + 1)]); + }, + [value, onChange] + ); + + const addRowHandler = useCallback(() => { + setAutoFocus(true); + onChange([...value, '']); + }, [value, onChange]); + + const onDragEndHandler = useCallback( + ({ source, destination }) => { + if (source && destination) { + const items = euiDragDropReorder(value, source.index, destination.index); + + onChange(items); + } + }, + [value, onChange] + ); + + const globalErrors = useMemo(() => { + return errors && errors.filter((err) => err.index === undefined).map(({ message }) => message); + }, [errors]); + + const indexedErrors = useMemo(() => { + if (!errors) { + return []; + } + return errors.reduce((acc, err) => { + if (err.index === undefined) { + return acc; + } + + if (!acc[err.index]) { + acc[err.index] = []; + } + + acc[err.index].push(err.message); + + return acc; + }, [] as string[][]); + }, [errors]); + + const isSortable = rows.length > 1; + return ( + + <> + {helpText} + + + + {rows.map((row, idx) => ( + + {isSortable ? ( + + ) : ( + <> + + {displayErrors(indexedErrors[idx])} + + )} + + ))} + + + {displayErrors(globalErrors)} + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx index b8028547910097..ea2e795d5fabbb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx @@ -20,10 +20,10 @@ import { EuiFlyoutFooter, EuiForm, EuiFormRow, - EuiComboBox, EuiCode, EuiCodeEditor, EuiLink, + EuiPanel, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; @@ -41,6 +41,7 @@ import { isDiffPathProtocol } from '../../../../../common/'; import { SettingsConfirmModal } from './confirm_modal'; import type { SettingsConfirmModalProps } from './confirm_modal'; +import { HostsInput } from './hosts_input'; import 'brace/mode/yaml'; import 'brace/theme/textmate'; @@ -59,37 +60,60 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { const [isLoading, setIsloading] = React.useState(false); const { notifications } = useStartServices(); - const fleetServerHostsInput = useComboInput([], (value) => { + const fleetServerHostsInput = useComboInput('fleetServerHostsComboBox', [], (value) => { if (value.length === 0) { return [ - i18n.translate('xpack.fleet.settings.fleetServerHostsEmptyError', { - defaultMessage: 'At least one URL is required', - }), + { + message: i18n.translate('xpack.fleet.settings.fleetServerHostsEmptyError', { + defaultMessage: 'At least one URL is required', + }), + }, ]; } - if (value.some((v) => !v.match(URL_REGEX))) { - return [ - i18n.translate('xpack.fleet.settings.fleetServerHostsError', { - defaultMessage: 'Invalid URL', - }), - ]; + + const res: Array<{ message: string; index: number }> = []; + value.forEach((val, idx) => { + if (!val.match(URL_REGEX)) { + res.push({ + message: i18n.translate('xpack.fleet.settings.fleetServerHostsError', { + defaultMessage: 'Invalid URL', + }), + index: idx, + }); + } + }); + if (res.length) { + return res; } + if (value.length && isDiffPathProtocol(value)) { return [ - i18n.translate('xpack.fleet.settings.fleetServerHostsDifferentPathOrProtocolError', { - defaultMessage: 'Protocol and path must be the same for each URL', - }), + { + message: i18n.translate( + 'xpack.fleet.settings.fleetServerHostsDifferentPathOrProtocolError', + { + defaultMessage: 'Protocol and path must be the same for each URL', + } + ), + }, ]; } }); - const elasticsearchUrlInput = useComboInput([], (value) => { - if (value.some((v) => !v.match(URL_REGEX))) { - return [ - i18n.translate('xpack.fleet.settings.elasticHostError', { - defaultMessage: 'Invalid URL', - }), - ]; + const elasticsearchUrlInput = useComboInput('esHostsComboxBox', [], (value) => { + const res: Array<{ message: string; index: number }> = []; + value.forEach((val, idx) => { + if (!val.match(URL_REGEX)) { + res.push({ + message: i18n.translate('xpack.fleet.settings.elasticHostError', { + defaultMessage: 'Invalid URL', + }), + index: idx, + }); + } + }); + if (res.length) { + return res; } }); @@ -264,91 +288,72 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { /> - - - - - ), - }} - /> - } - {...inputs.fleetServerHosts.formRowProps} - > - - - + + + + + ), + }} + /> + } + /> + - - - - ), + + + + + + + - } - {...inputs.elasticsearchUrl.formRowProps} - > - - - - - - + + ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts index 6314fbeb0c72eb..e4a517dbae9c8f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_input.ts @@ -5,11 +5,12 @@ * 2.0. */ -import React from 'react'; +import { useState, useCallback } from 'react'; +import type React from 'react'; export function useInput(defaultValue = '', validate?: (value: string) => string[] | undefined) { - const [value, setValue] = React.useState(defaultValue); - const [errors, setErrors] = React.useState(); + const [value, setValue] = useState(defaultValue); + const [errors, setErrors] = useState(); const onChange = (e: React.ChangeEvent) => { const newValue = e.target.value; @@ -50,31 +51,31 @@ export function useInput(defaultValue = '', validate?: (value: string) => string } export function useComboInput( + id: string, defaultValue = [], - validate?: (value: string[]) => string[] | undefined + validate?: (value: string[]) => Array<{ message: string; index?: number }> | undefined ) { - const [value, setValue] = React.useState(defaultValue); - const [errors, setErrors] = React.useState(); + const [value, setValue] = useState(defaultValue); + const [errors, setErrors] = useState | undefined>(); const isInvalid = errors !== undefined; + const onChange = useCallback( + (newValues: string[]) => { + setValue(newValues); + if (errors && validate && validate(newValues) === undefined) { + setErrors(undefined); + } + }, + [validate, errors] + ); + return { props: { - selectedOptions: value.map((val: string) => ({ label: val })), - onCreateOption: (newVal: any) => { - setValue([...value, newVal]); - }, - onChange: (newSelectedOptions: any[]) => { - const newValues = newSelectedOptions.map((option) => option.label); - setValue(newValues); - if (errors && validate && validate(newValues) === undefined) { - setErrors(undefined); - } - }, - isInvalid, - }, - formRowProps: { - error: errors, + id, + value, + onChange, + errors, isInvalid, }, value, diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json index 5dcff0ba942e12..cd29e7b9ee1cdc 100644 --- a/x-pack/plugins/index_management/kibana.json +++ b/x-pack/plugins/index_management/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["home", "licensing", "management", "features", "share"], + "requiredPlugins": ["home", "management", "features", "share"], "optionalPlugins": ["security", "usageCollection", "fleet"], "configPath": ["xpack", "index_management"], "requiredBundles": [ diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index d2ef5a7eed6e7a..35d25eb452b84d 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -5,11 +5,9 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, - Logger, PluginInitializerContext, ILegacyCustomClusterClient, } from 'src/core/server'; @@ -17,7 +15,7 @@ import { import { PLUGIN } from '../common/constants/plugin'; import { Dependencies } from './types'; import { ApiRoutes } from './routes'; -import { License, IndexDataEnricher } from './services'; +import { IndexDataEnricher } from './services'; import { isEsError, handleEsError, parseEsError } from './shared_imports'; import { elasticsearchJsPlugin } from './client/elasticsearch'; import type { IndexManagementRequestHandlerContext } from './types'; @@ -36,38 +34,20 @@ async function getCustomEsClient(getStartServices: CoreSetup['getStartServices'] export class IndexMgmtServerPlugin implements Plugin { private readonly apiRoutes: ApiRoutes; - private readonly license: License; - private readonly logger: Logger; private readonly indexDataEnricher: IndexDataEnricher; private dataManagementESClient?: ILegacyCustomClusterClient; constructor(initContext: PluginInitializerContext) { - this.logger = initContext.logger.get(); this.apiRoutes = new ApiRoutes(); - this.license = new License(); this.indexDataEnricher = new IndexDataEnricher(); } setup( { http, getStartServices }: CoreSetup, - { features, licensing, security }: Dependencies + { features, security }: Dependencies ): IndexManagementPluginSetup { const router = http.createRouter(); - this.license.setup( - { - pluginId: PLUGIN.id, - minimumLicenseType: PLUGIN.minimumLicenseType, - defaultErrorMessage: i18n.translate('xpack.idxMgmt.licenseCheckErrorMessage', { - defaultMessage: 'License check failed', - }), - }, - { - licensing, - logger: this.logger, - } - ); - features.registerElasticsearchFeature({ id: PLUGIN.id, management: { @@ -97,7 +77,6 @@ export class IndexMgmtServerPlugin implements Plugin security !== undefined && security.license.isEnabled(), }, diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts index a6c0592e035e79..4bf05b44c43a70 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/create.ts @@ -12,11 +12,7 @@ import { RouteDependencies } from '../../../types'; import { addBasePath } from '../index'; import { componentTemplateSchema } from './schema_validation'; -export const registerCreateRoute = ({ - router, - license, - lib: { isEsError }, -}: RouteDependencies): void => { +export const registerCreateRoute = ({ router, lib: { isEsError } }: RouteDependencies): void => { router.post( { path: addBasePath('/component_templates'), @@ -24,7 +20,7 @@ export const registerCreateRoute = ({ body: componentTemplateSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const serializedComponentTemplate = serializeComponentTemplate(req.body); @@ -73,6 +69,6 @@ export const registerCreateRoute = ({ throw error; } - }) + } ); }; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts index abf04da638cafc..d30f54f6e44adf 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/delete.ts @@ -14,7 +14,7 @@ const paramsSchema = schema.object({ names: schema.string(), }); -export const registerDeleteRoute = ({ router, license }: RouteDependencies): void => { +export const registerDeleteRoute = ({ router }: RouteDependencies): void => { router.delete( { path: addBasePath('/component_templates/{names}'), @@ -22,7 +22,7 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { names } = req.params; const componentNames = names.split(','); @@ -48,6 +48,6 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi ); return res.ok({ body: response }); - }) + } ); }; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts index 552aa5a9a2888c..a5d70e65f870a9 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts @@ -19,11 +19,11 @@ const paramsSchema = schema.object({ name: schema.string(), }); -export function registerGetAllRoute({ router, license, lib: { isEsError } }: RouteDependencies) { +export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDependencies) { // Get all component templates router.get( { path: addBasePath('/component_templates'), validate: false }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; try { @@ -56,7 +56,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou throw error; } - }) + } ); // Get single component template @@ -67,7 +67,7 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { name } = req.params; @@ -96,6 +96,6 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou throw error; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.test.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.test.ts index 62fb228a34cedf..eccf2d945785f2 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.test.ts @@ -8,7 +8,6 @@ import { httpServerMock, httpServiceMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from 'src/core/server'; -import { License } from '../../../services/license'; import { IndexDataEnricher } from '../../../services/index_data_enricher'; import { registerPrivilegesRoute } from './privileges'; @@ -47,9 +46,6 @@ describe('GET privileges', () => { registerPrivilegesRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, config: { isSecurityEnabled: () => true, }, @@ -118,9 +114,6 @@ describe('GET privileges', () => { registerPrivilegesRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, config: { isSecurityEnabled: () => false, }, diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts index 1ed6555eb38067..62ad93453091ec 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/privileges.ts @@ -17,13 +17,13 @@ const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } = return privileges; }, []); -export const registerPrivilegesRoute = ({ license, router, config }: RouteDependencies) => { +export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) => { router.get( { path: addBasePath('/component_templates/privileges'), validate: false, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const privilegesResult: Privileges = { hasAllPrivileges: true, missingPrivileges: { @@ -66,6 +66,6 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend } catch (e) { throw e; } - }) + } ); }; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts index 42b53ab6ee25b3..ee94b8f2b0082d 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/update.ts @@ -15,11 +15,7 @@ const paramsSchema = schema.object({ name: schema.string(), }); -export const registerUpdateRoute = ({ - router, - license, - lib: { isEsError }, -}: RouteDependencies): void => { +export const registerUpdateRoute = ({ router, lib: { isEsError } }: RouteDependencies): void => { router.put( { path: addBasePath('/component_templates/{name}'), @@ -28,7 +24,7 @@ export const registerUpdateRoute = ({ params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { name } = req.params; const { template, version, _meta } = req.body; @@ -57,6 +53,6 @@ export const registerUpdateRoute = ({ throw error; } - }) + } ); }; diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts index 476228cfce2fcf..49166f4823a026 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts @@ -15,13 +15,13 @@ const bodySchema = schema.object({ dataStreams: schema.arrayOf(schema.string()), }); -export function registerDeleteRoute({ router, license }: RouteDependencies) { +export function registerDeleteRoute({ router }: RouteDependencies) { router.post( { path: addBasePath('/delete_data_streams'), validate: { body: bodySchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { dataStreams } = req.body as TypeOf; @@ -48,6 +48,6 @@ export function registerDeleteRoute({ router, license }: RouteDependencies) { ); return res.ok({ body: response }); - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 9573b9cc6436f2..1ce7c14f0a2093 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -103,18 +103,13 @@ const getDataStreamsPrivileges = (client: ElasticsearchClient, names: string[]) }); }; -export function registerGetAllRoute({ - router, - license, - lib: { handleEsError }, - config, -}: RouteDependencies) { +export function registerGetAllRoute({ router, lib: { handleEsError }, config }: RouteDependencies) { const querySchema = schema.object({ includeStats: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])), }); router.get( { path: addBasePath('/data_streams'), validate: { query: querySchema } }, - license.guardApiRoute(async (ctx, req, response) => { + async (ctx, req, response) => { const { asCurrentUser } = ctx.core.elasticsearch.client; const includeStats = (req.query as TypeOf).includeStats === 'true'; @@ -151,16 +146,11 @@ export function registerGetAllRoute({ } catch (error) { return handleEsError({ error, response }); } - }) + } ); } -export function registerGetOneRoute({ - router, - license, - lib: { handleEsError }, - config, -}: RouteDependencies) { +export function registerGetOneRoute({ router, lib: { handleEsError }, config }: RouteDependencies) { const paramsSchema = schema.object({ name: schema.string(), }); @@ -169,7 +159,7 @@ export function registerGetOneRoute({ path: addBasePath('/data_streams/{name}'), validate: { params: paramsSchema }, }, - license.guardApiRoute(async (ctx, req, response) => { + async (ctx, req, response) => { const { name } = req.params as TypeOf; const { asCurrentUser } = ctx.core.elasticsearch.client; try { @@ -207,6 +197,6 @@ export function registerGetOneRoute({ } catch (error) { return handleEsError({ error, response }); } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index 2f5da4b1d8957a..593f0cda6886e3 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerClearCacheRoute({ router, license, lib }: RouteDependencies) { +export function registerClearCacheRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/clear_cache'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const payload = req.body as typeof bodySchema.type; const { indices = [] } = payload; @@ -40,6 +40,6 @@ export function registerClearCacheRoute({ router, license, lib }: RouteDependenc // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts index 1a0babfc3a5b1f..777adcd0557095 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerCloseRoute({ router, license, lib }: RouteDependencies) { +export function registerCloseRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/close'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const payload = req.body as typeof bodySchema.type; const { indices = [] } = payload; @@ -40,6 +40,6 @@ export function registerCloseRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts index 9a022d4595d1c0..914835089a4381 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerDeleteRoute({ router, license, lib }: RouteDependencies) { +export function registerDeleteRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/delete'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const body = req.body as typeof bodySchema.type; const { indices = [] } = body; @@ -40,6 +40,6 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts index b064f3520004a0..bb1759a034cc7e 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerFlushRoute({ router, license, lib }: RouteDependencies) { +export function registerFlushRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/flush'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const body = req.body as typeof bodySchema.type; const { indices = [] } = body; @@ -40,6 +40,6 @@ export function registerFlushRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index 1c14f660b98c67..6f0e8f0fec5673 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -15,7 +15,7 @@ const bodySchema = schema.object({ maxNumSegments: schema.maybe(schema.number()), }); -export function registerForcemergeRoute({ router, license, lib }: RouteDependencies) { +export function registerForcemergeRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/forcemerge'), @@ -23,7 +23,7 @@ export function registerForcemergeRoute({ router, license, lib }: RouteDependenc body: bodySchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { maxNumSegments, indices = [] } = req.body as typeof bodySchema.type; const params = { expandWildcards: 'none', @@ -47,6 +47,6 @@ export function registerForcemergeRoute({ router, license, lib }: RouteDependenc // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts index b669d78f2ba597..4b1281e0f21219 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerFreezeRoute({ router, license, lib }: RouteDependencies) { +export function registerFreezeRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/freeze'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const body = req.body as typeof bodySchema.type; const { indices = [] } = body; @@ -42,6 +42,6 @@ export function registerFreezeRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts index 0b253b9fe66c97..47c454e96c8e2a 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts @@ -9,26 +9,23 @@ import { fetchIndices } from '../../../lib/fetch_indices'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '../index'; -export function registerListRoute({ router, license, indexDataEnricher, lib }: RouteDependencies) { - router.get( - { path: addBasePath('/indices'), validate: false }, - license.guardApiRoute(async (ctx, req, res) => { - try { - const indices = await fetchIndices( - ctx.core.elasticsearch.legacy.client.callAsCurrentUser, - indexDataEnricher - ); - return res.ok({ body: indices }); - } catch (e) { - if (lib.isEsError(e)) { - return res.customError({ - statusCode: e.statusCode, - body: e, - }); - } - // Case: default - throw e; +export function registerListRoute({ router, indexDataEnricher, lib }: RouteDependencies) { + router.get({ path: addBasePath('/indices'), validate: false }, async (ctx, req, res) => { + try { + const indices = await fetchIndices( + ctx.core.elasticsearch.legacy.client.callAsCurrentUser, + indexDataEnricher + ); + return res.ok({ body: indices }); + } catch (e) { + if (lib.isEsError(e)) { + return res.customError({ + statusCode: e.statusCode, + body: e, + }); } - }) - ); + // Case: default + throw e; + } + }); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts index a35ddfcf4d91b2..cad57ce60de654 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerOpenRoute({ router, license, lib }: RouteDependencies) { +export function registerOpenRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/open'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const body = req.body as typeof bodySchema.type; const { indices = [] } = body; @@ -40,6 +40,6 @@ export function registerOpenRoute({ router, license, lib }: RouteDependencies) { // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index f69d2d90a5b8fc..e2c0155e280865 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerRefreshRoute({ router, license, lib }: RouteDependencies) { +export function registerRefreshRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/refresh'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const body = req.body as typeof bodySchema.type; const { indices = [] } = body; @@ -40,6 +40,6 @@ export function registerRefreshRoute({ router, license, lib }: RouteDependencies // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts index 04b7d760fc1d62..8d83cd21f427dd 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -17,15 +17,10 @@ const bodySchema = schema.maybe( }) ); -export function registerReloadRoute({ - router, - license, - indexDataEnricher, - lib, -}: RouteDependencies) { +export function registerReloadRoute({ router, indexDataEnricher, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/reload'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indexNames = [] } = (req.body as typeof bodySchema.type) ?? {}; try { @@ -45,6 +40,6 @@ export function registerReloadRoute({ // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts index 3cda4d6b5f1682..45102f4874129a 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts @@ -14,10 +14,10 @@ const bodySchema = schema.object({ indices: schema.arrayOf(schema.string()), }); -export function registerUnfreezeRoute({ router, license, lib }: RouteDependencies) { +export function registerUnfreezeRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/indices/unfreeze'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indices = [] } = req.body as typeof bodySchema.type; const params = { path: `/${encodeURIComponent(indices.join(','))}/_unfreeze`, @@ -37,6 +37,6 @@ export function registerUnfreezeRoute({ router, license, lib }: RouteDependencie // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts index f0b62bacdee426..406ceba16c8bda 100644 --- a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts @@ -21,10 +21,10 @@ function formatHit(hit: { [key: string]: { mappings: any } }, indexName: string) }; } -export function registerMappingRoute({ router, license, lib }: RouteDependencies) { +export function registerMappingRoute({ router, lib }: RouteDependencies) { router.get( { path: addBasePath('/mapping/{indexName}'), validate: { params: paramsSchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indexName } = req.params as typeof paramsSchema.type; const params = { expand_wildcards: 'none', @@ -48,6 +48,6 @@ export function registerMappingRoute({ router, license, lib }: RouteDependencies // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts index 7a661a9e9e4f49..276b326929e8f9 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts @@ -21,10 +21,10 @@ function formatHit(hit: { [key: string]: {} }) { return hit[key]; } -export function registerLoadRoute({ router, license, lib }: RouteDependencies) { +export function registerLoadRoute({ router, lib }: RouteDependencies) { router.get( { path: addBasePath('/settings/{indexName}'), validate: { params: paramsSchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indexName } = req.params as typeof paramsSchema.type; const params = { expandWildcards: 'none', @@ -50,6 +50,6 @@ export function registerLoadRoute({ router, license, lib }: RouteDependencies) { // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts index 4c153d6293a79f..b4f12b91083df9 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts @@ -16,13 +16,13 @@ const paramsSchema = schema.object({ indexName: schema.string(), }); -export function registerUpdateRoute({ router, license, lib }: RouteDependencies) { +export function registerUpdateRoute({ router, lib }: RouteDependencies) { router.put( { path: addBasePath('/settings/{indexName}'), validate: { body: bodySchema, params: paramsSchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indexName } = req.params as typeof paramsSchema.type; const params = { ignoreUnavailable: true, @@ -48,6 +48,6 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts index f8385711b55fe2..42a3012ea8e174 100644 --- a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts @@ -23,10 +23,10 @@ function formatHit(hit: { _shards: any; indices: { [key: string]: any } }, index }; } -export function registerStatsRoute({ router, license, lib }: RouteDependencies) { +export function registerStatsRoute({ router, lib }: RouteDependencies) { router.get( { path: addBasePath('/stats/{indexName}'), validate: { params: paramsSchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { indexName } = req.params as typeof paramsSchema.type; const params = { expand_wildcards: 'none', @@ -49,6 +49,6 @@ export function registerStatsRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts index 97e3c380e13ecd..d8a236bdebd155 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts @@ -15,10 +15,10 @@ import { saveTemplate, doesTemplateExist } from './lib'; const bodySchema = templateSchema; -export function registerCreateRoute({ router, license, lib }: RouteDependencies) { +export function registerCreateRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/index_templates'), validate: { body: bodySchema } }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const template = req.body as TemplateDeserialized; const { @@ -64,6 +64,6 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts index e258fafa534e31..083964dec9edcc 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts @@ -22,13 +22,13 @@ const bodySchema = schema.object({ ), }); -export function registerDeleteRoute({ router, license }: RouteDependencies) { +export function registerDeleteRoute({ router }: RouteDependencies) { router.post( { path: addBasePath('/delete_index_templates'), validate: { body: bodySchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { templates } = req.body as TypeOf; const response: { templatesDeleted: Array; errors: any[] } = { @@ -60,6 +60,6 @@ export function registerDeleteRoute({ router, license }: RouteDependencies) { ); return res.ok({ body: response }); - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts index 006532cfd4dbe1..bd000186d91c40 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts @@ -17,32 +17,29 @@ import { getCloudManagedTemplatePrefix } from '../../../lib/get_managed_template import { RouteDependencies } from '../../../types'; import { addBasePath } from '../index'; -export function registerGetAllRoute({ router, license }: RouteDependencies) { - router.get( - { path: addBasePath('/index_templates'), validate: false }, - license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.dataManagement!.client; - const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser); - - const legacyTemplatesEs = await callAsCurrentUser('indices.getTemplate'); - const { index_templates: templatesEs } = await callAsCurrentUser( - 'dataManagement.getComposableIndexTemplates' - ); - - const legacyTemplates = deserializeLegacyTemplateList( - legacyTemplatesEs, - cloudManagedTemplatePrefix - ); - const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix); - - const body = { - templates, - legacyTemplates, - }; - - return res.ok({ body }); - }) - ); +export function registerGetAllRoute({ router }: RouteDependencies) { + router.get({ path: addBasePath('/index_templates'), validate: false }, async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.dataManagement!.client; + const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser); + + const legacyTemplatesEs = await callAsCurrentUser('indices.getTemplate'); + const { index_templates: templatesEs } = await callAsCurrentUser( + 'dataManagement.getComposableIndexTemplates' + ); + + const legacyTemplates = deserializeLegacyTemplateList( + legacyTemplatesEs, + cloudManagedTemplatePrefix + ); + const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix); + + const body = { + templates, + legacyTemplates, + }; + + return res.ok({ body }); + }); } const paramsSchema = schema.object({ @@ -54,13 +51,13 @@ const querySchema = schema.object({ legacy: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])), }); -export function registerGetOneRoute({ router, license, lib }: RouteDependencies) { +export function registerGetOneRoute({ router, lib }: RouteDependencies) { router.get( { path: addBasePath('/index_templates/{name}'), validate: { params: paramsSchema, query: querySchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { name } = req.params as TypeOf; const { callAsCurrentUser } = ctx.dataManagement!.client; @@ -106,6 +103,6 @@ export function registerGetOneRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts index f4554bd2fb1fa6..0c3d8faea628cf 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_simulate_route.ts @@ -12,13 +12,13 @@ import { addBasePath } from '../index'; const bodySchema = schema.object({}, { unknowns: 'allow' }); -export function registerSimulateRoute({ router, license, lib }: RouteDependencies) { +export function registerSimulateRoute({ router, lib }: RouteDependencies) { router.post( { path: addBasePath('/index_templates/simulate'), validate: { body: bodySchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const template = req.body as TypeOf; @@ -42,6 +42,6 @@ export function registerSimulateRoute({ router, license, lib }: RouteDependencie // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts index f0070408768cbf..07a7d457f04737 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts @@ -18,13 +18,13 @@ const paramsSchema = schema.object({ name: schema.string(), }); -export function registerUpdateRoute({ router, license, lib }: RouteDependencies) { +export function registerUpdateRoute({ router, lib }: RouteDependencies) { router.put( { path: addBasePath('/index_templates/{name}'), validate: { body: bodySchema, params: paramsSchema }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; const { name } = req.params as typeof paramsSchema.type; const template = req.body as TemplateDeserialized; @@ -58,6 +58,6 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) // Case: default throw e; } - }) + } ); } diff --git a/x-pack/plugins/index_management/server/services/index.ts b/x-pack/plugins/index_management/server/services/index.ts index 3af5117e2b7aaa..576d7c46fa086a 100644 --- a/x-pack/plugins/index_management/server/services/index.ts +++ b/x-pack/plugins/index_management/server/services/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -export { License } from './license'; - export { IndexDataEnricher, Enricher } from './index_data_enricher'; diff --git a/x-pack/plugins/index_management/server/services/license.ts b/x-pack/plugins/index_management/server/services/license.ts deleted file mode 100644 index 0898c01a1e4669..00000000000000 --- a/x-pack/plugins/index_management/server/services/license.ts +++ /dev/null @@ -1,82 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Logger } from 'src/core/server'; -import type { KibanaRequest, KibanaResponseFactory, RequestHandler } from 'kibana/server'; - -import { LicensingPluginSetup } from '../../../licensing/server'; -import { LicenseType } from '../../../licensing/common/types'; -import type { IndexManagementRequestHandlerContext } from '../types'; - -export interface LicenseStatus { - isValid: boolean; - message?: string; -} - -interface SetupSettings { - pluginId: string; - minimumLicenseType: LicenseType; - defaultErrorMessage: string; -} - -export class License { - private licenseStatus: LicenseStatus = { - isValid: false, - message: 'Invalid License', - }; - - setup( - { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, - { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } - ) { - licensing.license$.subscribe((license) => { - const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === 'valid'; - - if (hasRequiredLicense) { - this.licenseStatus = { isValid: true }; - } else { - this.licenseStatus = { - isValid: false, - message: message || defaultErrorMessage, - }; - if (message) { - logger.info(message); - } - } - }); - } - - guardApiRoute( - handler: RequestHandler - ) { - const license = this; - - return function licenseCheck( - ctx: Context, - request: KibanaRequest, - response: KibanaResponseFactory - ) { - const licenseStatus = license.getStatus(); - - if (!licenseStatus.isValid) { - return response.customError({ - body: { - message: licenseStatus.message || '', - }, - statusCode: 403, - }); - } - - return handler(ctx, request, response); - }; - } - - getStatus() { - return this.licenseStatus; - } -} diff --git a/x-pack/plugins/index_management/server/types.ts b/x-pack/plugins/index_management/server/types.ts index 0854733d5db595..c980279d5bf306 100644 --- a/x-pack/plugins/index_management/server/types.ts +++ b/x-pack/plugins/index_management/server/types.ts @@ -14,7 +14,7 @@ import type { import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; -import { License, IndexDataEnricher } from './services'; +import { IndexDataEnricher } from './services'; import { isEsError, parseEsError, handleEsError } from './shared_imports'; export interface Dependencies { @@ -25,7 +25,6 @@ export interface Dependencies { export interface RouteDependencies { router: IndexManagementRouter; - license: License; config: { isSecurityEnabled: () => boolean; }; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx index 901a4b6a8383e3..7f1636b00d24e3 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx @@ -113,7 +113,7 @@ export const Template = (args) => ; The purpose of this component is to allow you, the developer, to have your very own Log Stream in your plugin. -The component is exposed through `infra/public`. Since Kibana uses relative paths is up to you to find how to import it (sorry). +The component is exposed through `infra/public`. Since Kibana uses relative paths, it is up to you to find how to import it (sorry). ```tsx import { LogStream } from '../../../../../../infra/public'; @@ -124,8 +124,9 @@ import { LogStream } from '../../../../../../infra/public'; To use the component your plugin needs to follow certain criteria: -- Ensure `"infra"` is specified as a `requiredPlugins` in your plugin's `kibana.json`. -- Ensure the `` component is mounted inside the hiearchy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). +- Ensure `"infra"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`. +- Ensure the `` component is mounted inside the hierachy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). At a minimum, the kibana-react provider must pass `http` (from core start services) and `data` (from core plugin start dependencies). +- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/master/src/plugins/kibana_react/common/eui_styled_components.tsx). ## Usage @@ -354,25 +355,30 @@ The infra plugin has the concept of a "source configuration", a collection of se The `` component will use the `"default"` source configuration. If you want to use your own configuration, you need to first create it when you initialize your plugin, and then specify it in the `` component with the `sourceId` prop. ```tsx -// Your `plugin/init.ts` +// Your `server/plugin.ts` class MyPlugin { // ... setup(core, plugins) { plugins.infra.defineInternalSourceConfiguration( 'my_source', // ID for your source configuration { - logAlias: 'some-index-*', // Optional. what ES index to query. + name: 'some-name', + description: 'some description', + logIndices: { // Also accepts an `index_pattern` type with `indexPatternId` + type: 'index_name', + indexName: 'some-index', + }, logColumns: [ - { timestampColumn: { id: '...uuid4' }, // The `@timestamp` column. - { fieldColumn: { id: '...uuid4', field: 'some_field' }}, // Any column(s) you want. - { messageColumn: { id: '...uuid' }} // The `message` column. + { timestampColumn: { id: '...uuid4' }, // The `@timestamp` column. `id` is an arbitrary string identifier. + { fieldColumn: { id: '...uuid4', field: 'some_field' }}, // Any column(s) you want. `id` is an arbitrary string identifier. + { messageColumn: { id: '...uuid' }} // The `message` column. `id` is an arbitrary string identifier. ] } ); } } -// Somewhere else on your code +// Somewhere else in your client-side code { - private readonly logger: Logger; - private readonly license: License; private readonly apiRoutes: ApiRoutes; - constructor({ logger }: PluginInitializerContext) { - this.logger = logger.get(); - this.license = new License(); + constructor() { this.apiRoutes = new ApiRoutes(); } - public setup({ http }: CoreSetup, { licensing, security, features }: Dependencies) { - this.logger.debug('ingest_pipelines: setup'); - + public setup({ http }: CoreSetup, { security, features }: Dependencies) { const router = http.createRouter(); - this.license.setup( - { - pluginId: PLUGIN_ID, - minimumLicenseType: PLUGIN_MIN_LICENSE_TYPE, - defaultErrorMessage: i18n.translate('xpack.ingestPipelines.licenseCheckErrorMessage', { - defaultMessage: 'License check failed', - }), - }, - { - licensing, - logger: this.logger, - } - ); - features.registerElasticsearchFeature({ id: 'ingest_pipelines', management: { @@ -61,7 +36,6 @@ export class IngestPipelinesPlugin implements Plugin { this.apiRoutes.setup({ router, - license: this.license, config: { isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), }, diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index 388c82aa34b3d1..b078ca051a2724 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -20,7 +20,6 @@ const bodySchema = schema.object({ export const registerCreateRoute = ({ router, - license, lib: { handleEsError }, }: RouteDependencies): void => { router.post( @@ -30,7 +29,7 @@ export const registerCreateRoute = ({ body: bodySchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const pipeline = req.body as Pipeline; @@ -74,6 +73,6 @@ export const registerCreateRoute = ({ } catch (error) { return handleEsError({ error, response: res }); } - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts index 8cc7d7044ad08e..1ffa5adabd83b3 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts @@ -14,7 +14,7 @@ const paramsSchema = schema.object({ names: schema.string(), }); -export const registerDeleteRoute = ({ router, license }: RouteDependencies): void => { +export const registerDeleteRoute = ({ router }: RouteDependencies): void => { router.delete( { path: `${API_BASE_PATH}/{names}`, @@ -22,7 +22,7 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const { names } = req.params; const pipelineNames = names.split(','); @@ -48,6 +48,6 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi ); return res.ok({ body: response }); - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts index 324bcdd3edb462..6f7233c70dbfe8 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -17,7 +17,6 @@ const paramsSchema = schema.object({ export const registerDocumentsRoute = ({ router, - license, lib: { handleEsError }, }: RouteDependencies): void => { router.get( @@ -27,7 +26,7 @@ export const registerDocumentsRoute = ({ params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const { index, id } = req.params; @@ -46,6 +45,6 @@ export const registerDocumentsRoute = ({ } catch (error) { return handleEsError({ error, response: res }); } - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index 853bd1c7dde238..b512ebda5ecdb2 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -15,32 +15,25 @@ const paramsSchema = schema.object({ name: schema.string(), }); -export const registerGetRoutes = ({ - router, - license, - lib: { handleEsError }, -}: RouteDependencies): void => { +export const registerGetRoutes = ({ router, lib: { handleEsError } }: RouteDependencies): void => { // Get all pipelines - router.get( - { path: API_BASE_PATH, validate: false }, - license.guardApiRoute(async (ctx, req, res) => { - const { client: clusterClient } = ctx.core.elasticsearch; + router.get({ path: API_BASE_PATH, validate: false }, async (ctx, req, res) => { + const { client: clusterClient } = ctx.core.elasticsearch; - try { - const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline(); + try { + const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline(); - return res.ok({ body: deserializePipelines(pipelines) }); - } catch (error) { - const esErrorResponse = handleEsError({ error, response: res }); - if (esErrorResponse.status === 404) { - // ES returns 404 when there are no pipelines - // Instead, we return an empty array and 200 status back to the client - return res.ok({ body: [] }); - } - return esErrorResponse; + return res.ok({ body: deserializePipelines(pipelines) }); + } catch (error) { + const esErrorResponse = handleEsError({ error, response: res }); + if (esErrorResponse.status === 404) { + // ES returns 404 when there are no pipelines + // Instead, we return an empty array and 200 status back to the client + return res.ok({ body: [] }); } - }) - ); + return esErrorResponse; + } + }); // Get single pipeline router.get( @@ -50,7 +43,7 @@ export const registerGetRoutes = ({ params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; @@ -68,6 +61,6 @@ export const registerGetRoutes = ({ } catch (error) { return handleEsError({ error, response: res }); } - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index e1e4b2d3d28866..5368b59b35a41a 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -17,13 +17,13 @@ const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } = return privileges; }, []); -export const registerPrivilegesRoute = ({ license, router, config }: RouteDependencies) => { +export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) => { router.get( { path: `${API_BASE_PATH}/privileges`, validate: false, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const privilegesResult: Privileges = { hasAllPrivileges: true, missingPrivileges: { @@ -51,6 +51,6 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend privilegesResult.hasAllPrivileges = hasAllPrivileges; return res.ok({ body: privilegesResult }); - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index a1d0a4ec2e3d32..f697a38e21561e 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -19,7 +19,6 @@ const bodySchema = schema.object({ export const registerSimulateRoute = ({ router, - license, lib: { handleEsError }, }: RouteDependencies): void => { router.post( @@ -29,7 +28,7 @@ export const registerSimulateRoute = ({ body: bodySchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const { pipeline, documents, verbose } = req.body; @@ -47,6 +46,6 @@ export const registerSimulateRoute = ({ } catch (error) { return handleEsError({ error, response: res }); } - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 0d3e2a37795273..35af1395f5e37b 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -19,7 +19,6 @@ const paramsSchema = schema.object({ export const registerUpdateRoute = ({ router, - license, lib: { handleEsError }, }: RouteDependencies): void => { router.put( @@ -30,7 +29,7 @@ export const registerUpdateRoute = ({ params: paramsSchema, }, }, - license.guardApiRoute(async (ctx, req, res) => { + async (ctx, req, res) => { const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -54,6 +53,6 @@ export const registerUpdateRoute = ({ } catch (error) { return handleEsError({ error, response: res }); } - }) + } ); }; diff --git a/x-pack/plugins/ingest_pipelines/server/services/license.ts b/x-pack/plugins/ingest_pipelines/server/services/license.ts deleted file mode 100644 index 05878b170ff1d0..00000000000000 --- a/x-pack/plugins/ingest_pipelines/server/services/license.ts +++ /dev/null @@ -1,84 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Logger } from 'src/core/server'; -import { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'kibana/server'; - -import { LicensingPluginSetup } from '../../../licensing/server'; -import { LicenseType } from '../../../licensing/common/types'; - -export interface LicenseStatus { - isValid: boolean; - message?: string; -} - -interface SetupSettings { - pluginId: string; - minimumLicenseType: LicenseType; - defaultErrorMessage: string; -} - -export class License { - private licenseStatus: LicenseStatus = { - isValid: false, - message: 'Invalid License', - }; - - setup( - { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, - { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } - ) { - licensing.license$.subscribe((license) => { - const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === 'valid'; - - if (hasRequiredLicense) { - this.licenseStatus = { isValid: true }; - } else { - this.licenseStatus = { - isValid: false, - message: message || defaultErrorMessage, - }; - if (message) { - logger.info(message); - } - } - }); - } - - guardApiRoute(handler: RequestHandler) { - const license = this; - - return function licenseCheck( - ctx: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) { - const licenseStatus = license.getStatus(); - - if (!licenseStatus.isValid) { - return response.customError({ - body: { - message: licenseStatus.message || '', - }, - statusCode: 403, - }); - } - - return handler(ctx, request, response); - }; - } - - getStatus() { - return this.licenseStatus; - } -} diff --git a/x-pack/plugins/ingest_pipelines/server/types.ts b/x-pack/plugins/ingest_pipelines/server/types.ts index 912a0c88eef62a..efc0679708a5d9 100644 --- a/x-pack/plugins/ingest_pipelines/server/types.ts +++ b/x-pack/plugins/ingest_pipelines/server/types.ts @@ -6,21 +6,17 @@ */ import { IRouter } from 'src/core/server'; -import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; -import { License } from './services'; import { handleEsError } from './shared_imports'; export interface Dependencies { security: SecurityPluginSetup; features: FeaturesPluginSetup; - licensing: LicensingPluginSetup; } export interface RouteDependencies { router: IRouter; - license: License; config: { isSecurityEnabled: () => boolean; }; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx index f2640c5c32acf9..e84f6fd43418be 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx @@ -12,12 +12,13 @@ import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddab const defaultSavedObjectId = '1234'; describe('Mounter', () => { + const byValueFlag = { allowByValueEmbeddables: true }; describe('loadDocument', () => { it('does not load a document if there is no initial input', async () => { const services = makeDefaultServices(); const redirectCallback = jest.fn(); const lensStore = mockLensStore({ data: services.data }); - await loadDocument(redirectCallback, undefined, services, lensStore); + await loadDocument(redirectCallback, undefined, services, lensStore, undefined, byValueFlag); expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); }); @@ -39,7 +40,9 @@ describe('Mounter', () => { redirectCallback, { savedObjectId: defaultSavedObjectId } as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); @@ -76,7 +79,9 @@ describe('Mounter', () => { redirectCallback, { savedObjectId: defaultSavedObjectId } as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); @@ -85,7 +90,9 @@ describe('Mounter', () => { redirectCallback, { savedObjectId: defaultSavedObjectId } as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); @@ -96,7 +103,9 @@ describe('Mounter', () => { redirectCallback, { savedObjectId: '5678' } as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); @@ -116,7 +125,9 @@ describe('Mounter', () => { redirectCallback, { savedObjectId: defaultSavedObjectId } as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); expect(services.attributeService.unwrapAttributes).toHaveBeenCalledWith({ @@ -136,7 +147,9 @@ describe('Mounter', () => { redirectCallback, ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput, services, - lensStore + lensStore, + undefined, + byValueFlag ); }); diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 708573e843fcf4..3e56fbb2003cb4 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -17,6 +17,7 @@ import { i18n } from '@kbn/i18n'; import { DashboardFeatureFlagConfig } from 'src/plugins/dashboard/public'; import { Provider } from 'react-redux'; import { uniq, isEqual } from 'lodash'; +import { EmbeddableEditorState } from 'src/plugins/embeddable/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; @@ -71,6 +72,8 @@ export async function mountApp( const historyLocationState = params.history.location.state as HistoryLocationState; const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(APP_ID); + const dashboardFeatureFlag = await getByValueFeatureFlag(); + const lensServices: LensAppServices = { data, storage, @@ -92,7 +95,7 @@ export async function mountApp( }, // Temporarily required until the 'by value' paradigm is default. - dashboardFeatureFlag: await getByValueFeatureFlag(), + dashboardFeatureFlag, }; addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks); @@ -172,7 +175,6 @@ export async function mountApp( if (!initialContext) { data.query.filterManager.setAppFilters([]); } - const preloadedState = getPreloadedState({ query: data.query.queryString.getQuery(), // Do not use app-specific filters from previous app, @@ -180,7 +182,7 @@ export async function mountApp( filters: !initialContext ? data.query.filterManager.getGlobalFilters() : data.query.filterManager.getFilters(), - searchSessionId: data.search.session.start(), + searchSessionId: data.search.session.getSessionId(), resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), isLinkedToOriginatingApp: Boolean(embeddableEditorIncomingState?.originatingApp), }); @@ -197,7 +199,14 @@ export async function mountApp( ); trackUiEvent('loaded'); const initialInput = getInitialInput(props.id, props.editByValue); - loadDocument(redirectCallback, initialInput, lensServices, lensStore); + loadDocument( + redirectCallback, + initialInput, + lensServices, + lensStore, + embeddableEditorIncomingState, + dashboardFeatureFlag + ); return ( { - data.search.session.clear(); unmountComponentAtNode(params.element); unlistenParentHistory(); lensStore.dispatch(navigateAway()); @@ -276,7 +284,9 @@ export function loadDocument( redirectCallback: (savedObjectId?: string) => void, initialInput: LensEmbeddableInput | undefined, lensServices: LensAppServices, - lensStore: LensRootStore + lensStore: LensRootStore, + embeddableEditorIncomingState: EmbeddableEditorState | undefined, + dashboardFeatureFlag: DashboardFeatureFlagConfig ) { const { attributeService, chrome, notifications, data } = lensServices; const { persistedDoc } = lensStore.getState().app; @@ -317,12 +327,20 @@ export function loadDocument( data.query.filterManager.setAppFilters( injectFilterReferences(doc.state.filters, doc.references) ); + const currentSessionId = data.search.session.getSessionId(); lensStore.dispatch( setState({ query: doc.state.query, isAppLoading: false, indexPatternsForTopNav: indexPatterns, lastKnownDoc: doc, + searchSessionId: + dashboardFeatureFlag.allowByValueEmbeddables && + Boolean(embeddableEditorIncomingState?.originatingApp) && + !(initialInput as LensByReferenceInput)?.savedObjectId && + currentSessionId + ? currentSessionId + : data.search.session.start(), ...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null), }) ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap index afc69c2e8861f9..a4be46f61990b6 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/components/__snapshots__/table_basic.test.tsx.snap @@ -13,6 +13,13 @@ exports[`DatatableComponent it renders actions column when there are row actions "b": "left", "c": "right", }, + "getColorForValue": [MockFunction], + "minMaxByColumnId": Object { + "c": Object { + "max": 3, + "min": 3, + }, + }, "rowHasRowClickTriggerActions": Array [ true, true, @@ -244,6 +251,13 @@ exports[`DatatableComponent it renders the title and value 1`] = ` "b": "left", "c": "right", }, + "getColorForValue": [MockFunction], + "minMaxByColumnId": Object { + "c": Object { + "max": 3, + "min": 3, + }, + }, "rowHasRowClickTriggerActions": undefined, "table": Object { "columns": Array [ @@ -462,6 +476,13 @@ exports[`DatatableComponent it should not render actions on header when it is in "b": "left", "c": "right", }, + "getColorForValue": [MockFunction], + "minMaxByColumnId": Object { + "c": Object { + "max": 3, + "min": 3, + }, + }, "rowHasRowClickTriggerActions": Array [ false, false, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx index 9bc982ebd9944b..67255dc8a953ea 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx @@ -11,6 +11,12 @@ import { DataContext } from './table_basic'; import { createGridCell } from './cell_value'; import { FieldFormat } from 'src/plugins/data/public'; import { Datatable } from 'src/plugins/expressions/public'; +import { IUiSettingsClient } from 'kibana/public'; +import { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; +import { Args, ColumnConfigArg } from '../expression'; +import { DataContextType } from './types'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; describe('datatable cell renderer', () => { const table: Datatable = { @@ -30,7 +36,9 @@ describe('datatable cell renderer', () => { { a: { convert: (x) => `formatted ${x}` } as FieldFormat, }, - DataContext + { columns: [], sortingColumnId: '', sortingDirection: 'none' }, + DataContext, + ({ get: jest.fn() } as unknown) as IUiSettingsClient ); it('renders formatted value', () => { @@ -78,4 +86,111 @@ describe('datatable cell renderer', () => { ); expect(cell.find('.lnsTableCell').prop('className')).toContain('--right'); }); + + describe('dynamic coloring', () => { + const paletteRegistry = chartPluginMock.createPaletteRegistry(); + const customPalette = paletteRegistry.get('custom'); + + function getCellRenderer(columnConfig: Args) { + return createGridCell( + { + a: { convert: (x) => `formatted ${x}` } as FieldFormat, + }, + columnConfig, + DataContext, + ({ get: jest.fn() } as unknown) as IUiSettingsClient + ); + } + function getColumnConfiguration(): Args { + return { + title: 'myData', + columns: [ + { + columnId: 'a', + colorMode: 'none', + palette: { + type: 'palette', + name: 'custom', + params: { + colors: ['#aaa', '#bbb', '#ccc', '#ddd', '#eee'], + gradient: false, + stops: [20, 40, 60, 80, 100], + range: 'percent', + rangeMin: 0, + rangeMax: 100, + }, + }, + type: 'lens_datatable_column', + } as ColumnConfigArg, + ], + sortingColumnId: '', + sortingDirection: 'none', + }; + } + + function flushEffect(component: ReactWrapper) { + return act(async () => { + await component; + await new Promise((r) => setImmediate(r)); + component.update(); + }); + } + + async function renderCellComponent(columnConfig: Args, context: Partial = {}) { + const CellRendererWithPalette = getCellRenderer(columnConfig); + const setCellProps = jest.fn(); + + const cell = mountWithIntl( + 123 */ } }, + getColorForValue: customPalette.getColorForValue, + ...context, + }} + > + + + ); + + await flushEffect(cell); + + return { setCellProps, cell }; + } + + it('ignores coloring when colorMode is set to "none"', async () => { + const { setCellProps } = await renderCellComponent(getColumnConfiguration()); + + expect(setCellProps).not.toHaveBeenCalled(); + }); + + it('should set the coloring of the cell when enabled', async () => { + const columnConfig = getColumnConfiguration(); + columnConfig.columns[0].colorMode = 'cell'; + + const { setCellProps } = await renderCellComponent(columnConfig, {}); + + expect(setCellProps).toHaveBeenCalledWith({ + style: expect.objectContaining({ backgroundColor: 'blue' }), + }); + }); + + it('should set the coloring of the text when enabled', async () => { + const columnConfig = getColumnConfiguration(); + columnConfig.columns[0].colorMode = 'text'; + + const { setCellProps } = await renderCellComponent(columnConfig, {}); + + expect(setCellProps).toHaveBeenCalledWith({ + style: expect.objectContaining({ color: 'blue' }), + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index 2261dd06b532ba..a6c50f00cb77fd 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -5,30 +5,74 @@ * 2.0. */ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { IUiSettingsClient } from 'kibana/public'; import type { FormatFactory } from '../../types'; import type { DataContextType } from './types'; +import { ColumnConfig } from './table_basic'; +import { getContrastColor } from '../../shared_components/coloring/utils'; +import { getOriginalId } from '../transpose_helpers'; export const createGridCell = ( formatters: Record>, - DataContext: React.Context -) => ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => { - const { table, alignments } = useContext(DataContext); - const rowValue = table?.rows[rowIndex][columnId]; - const content = formatters[columnId]?.convert(rowValue, 'html'); - const currentAlignment = alignments && alignments[columnId]; - const alignmentClassName = `lnsTableCell--${currentAlignment}`; + columnConfig: ColumnConfig, + DataContext: React.Context, + uiSettings: IUiSettingsClient +) => { + // Changing theme requires a full reload of the page, so we can cache here + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + return ({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) => { + const { table, alignments, minMaxByColumnId, getColorForValue } = useContext(DataContext); + const rowValue = table?.rows[rowIndex][columnId]; + const content = formatters[columnId]?.convert(rowValue, 'html'); + const currentAlignment = alignments && alignments[columnId]; + const alignmentClassName = `lnsTableCell--${currentAlignment}`; - return ( -
- ); + const { colorMode, palette } = + columnConfig.columns.find(({ columnId: id }) => id === columnId) || {}; + + useEffect(() => { + const originalId = getOriginalId(columnId); + if (minMaxByColumnId?.[originalId]) { + if (colorMode !== 'none' && palette?.params && getColorForValue) { + // workout the bucket the value belongs to + const color = getColorForValue(rowValue, palette.params, minMaxByColumnId[originalId]); + if (color) { + const style = { [colorMode === 'cell' ? 'backgroundColor' : 'color']: color }; + if (colorMode === 'cell' && color) { + style.color = getContrastColor(color, IS_DARK_THEME); + } + setCellProps({ + style, + }); + } + } + } + // make sure to clean it up when something change + // this avoids cell's styling to stick forever + return () => { + if (minMaxByColumnId?.[originalId]) { + setCellProps({ + style: { + backgroundColor: undefined, + color: undefined, + }, + }); + } + }; + }, [rowValue, columnId, setCellProps, colorMode, palette, minMaxByColumnId, getColorForValue]); + + return ( +
+ ); + }; }; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.scss b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.scss new file mode 100644 index 00000000000000..504adb05e57d7b --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.scss @@ -0,0 +1,7 @@ +.lnsDynamicColoringRow { + align-items: center; +} + +.lnsDynamicColoringClickable { + cursor: pointer; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx index e0d31a3ed02012..88948e9a7615b8 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -12,12 +12,18 @@ import { DatatableVisualizationState } from '../visualization'; import { createMockDatasource, createMockFramePublicAPI } from '../../editor_frame_service/mocks'; import { mountWithIntl } from '@kbn/test/jest'; import { TableDimensionEditor } from './dimension_editor'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { PaletteRegistry } from 'src/plugins/charts/public'; +import { PalettePanelContainer } from './palette_panel_container'; +import { act } from 'react-dom/test-utils'; describe('data table dimension editor', () => { let frame: FramePublicAPI; let state: DatatableVisualizationState; let setState: (newState: DatatableVisualizationState) => void; - let props: VisualizationDimensionEditorProps; + let props: VisualizationDimensionEditorProps & { + paletteService: PaletteRegistry; + }; function testState(): DatatableVisualizationState { return { @@ -59,6 +65,8 @@ describe('data table dimension editor', () => { layerId: 'first', state, setState, + paletteService: chartPluginMock.createPaletteRegistry(), + panelRef: React.createRef(), }; }); @@ -72,17 +80,23 @@ describe('data table dimension editor', () => { it('should render default alignment for number', () => { frame.activeData!.first.columns[0].meta.type = 'number'; const instance = mountWithIntl(); - expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( - expect.stringContaining('right') - ); + expect( + instance + .find('[data-test-subj="lnsDatatable_alignment_groups"]') + .find(EuiButtonGroup) + .prop('idSelected') + ).toEqual(expect.stringContaining('right')); }); it('should render specific alignment', () => { state.columns[0].alignment = 'center'; const instance = mountWithIntl(); - expect(instance.find(EuiButtonGroup).prop('idSelected')).toEqual( - expect.stringContaining('center') - ); + expect( + instance + .find('[data-test-subj="lnsDatatable_alignment_groups"]') + .find(EuiButtonGroup) + .prop('idSelected') + ).toEqual(expect.stringContaining('center')); }); it('should set state for the right column', () => { @@ -95,7 +109,10 @@ describe('data table dimension editor', () => { }, ]; const instance = mountWithIntl(); - instance.find(EuiButtonGroup).prop('onChange')('center'); + instance + .find('[data-test-subj="lnsDatatable_alignment_groups"]') + .find(EuiButtonGroup) + .prop('onChange')('center'); expect(setState).toHaveBeenCalledWith({ ...state, columns: [ @@ -109,4 +126,90 @@ describe('data table dimension editor', () => { ], }); }); + + it('should not show the dynamic coloring option for non numeric columns', () => { + const instance = mountWithIntl(); + expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]').exists()).toBe( + false + ); + expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( + false + ); + }); + + it('should set the dynamic coloring default to "none"', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + const instance = mountWithIntl(); + expect( + instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]') + .find(EuiButtonGroup) + .prop('idSelected') + ).toEqual(expect.stringContaining('none')); + + expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( + false + ); + }); + + it('should show the dynamic palette display ony when colorMode is different from "none"', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + state.columns[0].colorMode = 'text'; + const instance = mountWithIntl(); + expect( + instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]') + .find(EuiButtonGroup) + .prop('idSelected') + ).toEqual(expect.stringContaining('text')); + + expect(instance.find('[data-test-subj="lnsDatatable_dynamicColoring_palette"]').exists()).toBe( + true + ); + }); + + it('should set the coloring mode to the right column', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + state.columns = [ + { + columnId: 'foo', + }, + { + columnId: 'bar', + }, + ]; + const instance = mountWithIntl(); + instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_groups"]') + .find(EuiButtonGroup) + .prop('onChange')('cell'); + expect(setState).toHaveBeenCalledWith({ + ...state, + columns: [ + { + columnId: 'foo', + colorMode: 'cell', + palette: expect.objectContaining({ type: 'palette' }), + }, + { + columnId: 'bar', + }, + ], + }); + }); + + it('should open the palette panel when "Settings" link is clicked in the palette input', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + state.columns[0].colorMode = 'cell'; + const instance = mountWithIntl(); + + act(() => + (instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_trigger"]') + .first() + .prop('onClick') as () => void)?.() + ); + + expect(instance.find(PalettePanelContainer).exists()).toBe(true); + }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index a750744811790f..76c47a9c743c51 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -5,36 +5,91 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; +import { + EuiFormRow, + EuiSwitch, + EuiButtonGroup, + htmlIdGenerator, + EuiColorPaletteDisplay, + EuiFlexItem, + EuiFlexGroup, + EuiButtonEmpty, +} from '@elastic/eui'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; import { getOriginalId } from '../transpose_helpers'; +import { + CustomizablePalette, + applyPaletteParams, + defaultPaletteParams, + FIXED_PROGRESSION, + getStopsForFixedMode, +} from '../../shared_components/'; +import { PalettePanelContainer } from './palette_panel_container'; +import { findMinMaxByColumnId } from './shared_utils'; +import './dimension_editor.scss'; const idPrefix = htmlIdGenerator()(); +type ColumnType = DatatableVisualizationState['columns'][number]; + +function updateColumnWith( + state: DatatableVisualizationState, + columnId: string, + newColumnProps: Partial +) { + return state.columns.map((currentColumn) => { + if (currentColumn.columnId === columnId) { + return { ...currentColumn, ...newColumnProps }; + } else { + return currentColumn; + } + }); +} + export function TableDimensionEditor( - props: VisualizationDimensionEditorProps + props: VisualizationDimensionEditorProps & { + paletteService: PaletteRegistry; + } ) { const { state, setState, frame, accessor } = props; const column = state.columns.find(({ columnId }) => accessor === columnId); + const [isPaletteOpen, setIsPaletteOpen] = useState(false); if (!column) return null; if (column.isTransposed) return null; + const currentData = frame.activeData?.[state.layerId]; + // either read config state or use same logic as chart itself - const currentAlignment = - column?.alignment || - (frame.activeData && - frame.activeData[state.layerId]?.columns.find( - (col) => col.id === accessor || getOriginalId(col.id) === accessor - )?.meta.type === 'number' - ? 'right' - : 'left'); + const isNumericField = + currentData?.columns.find((col) => col.id === accessor || getOriginalId(col.id) === accessor) + ?.meta.type === 'number'; + + const currentAlignment = column?.alignment || (isNumericField ? 'right' : 'left'); + const currentColorMode = column?.colorMode || 'none'; + const hasDynamicColoring = currentColorMode !== 'none'; const visibleColumnsCount = state.columns.filter((c) => !c.hidden).length; + const hasTransposedColumn = state.columns.some(({ isTransposed }) => isTransposed); + const columnsToCheck = hasTransposedColumn + ? currentData?.columns.filter(({ id }) => getOriginalId(id) === accessor).map(({ id }) => id) || + [] + : [accessor]; + const minMaxByColumnId = findMinMaxByColumnId(columnsToCheck, currentData); + const currentMinMax = minMaxByColumnId[accessor]; + + const activePalette = column?.palette || { + type: 'palette', + name: defaultPaletteParams.name, + }; + // need to tell the helper that the colorStops are required to display + const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); + return ( <> { - const newMode = id.replace(idPrefix, '') as 'left' | 'right' | 'center'; - const newColumns = state.columns.map((currentColumn) => { - if (currentColumn.columnId === accessor) { - return { - ...currentColumn, - alignment: newMode, - }; - } else { - return currentColumn; - } + const newMode = id.replace(idPrefix, '') as ColumnType['alignment']; + setState({ + ...state, + columns: updateColumnWith(state, accessor, { alignment: newMode }), }); - setState({ ...state, columns: newColumns }); }} /> @@ -127,6 +175,135 @@ export function TableDimensionEditor( /> )} + {isNumericField && ( + <> + + { + const newMode = id.replace(idPrefix, '') as ColumnType['colorMode']; + const params: Partial = { + colorMode: newMode, + }; + if (!column?.palette && newMode !== 'none') { + params.palette = { + ...activePalette, + params: { + ...activePalette.params, + // that's ok, at first open we're going to throw them away and recompute + stops: displayStops, + }, + }; + } + // clear up when switching to no coloring + if (column?.palette && newMode === 'none') { + params.palette = undefined; + } + setState({ + ...state, + columns: updateColumnWith(state, accessor, params), + }); + }} + /> + + {hasDynamicColoring && ( + + + + { + setIsPaletteOpen(!isPaletteOpen); + }} + /> + + + { + setIsPaletteOpen(!isPaletteOpen); + }} + size="xs" + flush="both" + > + {i18n.translate('xpack.lens.paletteTableGradient.customize', { + defaultMessage: 'Edit', + })} + + setIsPaletteOpen(!isPaletteOpen)} + > + { + setState({ + ...state, + columns: updateColumnWith(state, accessor, { palette: newPalette }), + }); + }} + /> + + + + + )} + + )} ); } diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.scss b/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.scss new file mode 100644 index 00000000000000..db14d064d1881e --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.scss @@ -0,0 +1,53 @@ +@import '@elastic/eui/src/components/flyout/variables'; +@import '@elastic/eui/src/components/flyout/mixins'; + +.lnsPalettePanelContainer { + // Use the EuiFlyout style + @include euiFlyout; + // But with custom positioning to keep it within the sidebar contents + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; + animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; + // making just a bit higher than the dimension flyout to stack on top of it + z-index: $euiZLevel3 + 1 +} + +.lnsPalettePanelContainer__footer { + padding: $euiSizeS; +} + +.lnsPalettePanelContainer__header { + padding: $euiSizeS $euiSizeXS; +} + +.lnsPalettePanelContainer__headerTitle { + padding: $euiSizeS $euiSizeXS; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} + +.lnsPalettePanelContainer__headerLink { + &:focus-within { + background-color: transparentize($euiColorVis1, .9); + + .lnsPalettePanelContainer__headerTitle { + text-decoration: underline; + } + } +} + +.lnsPalettePanelContainer__backIcon { + &:hover { + transform: none !important; // sass-lint:disable-line no-important + } + + &:focus { + background-color: transparent; + } +} diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.tsx new file mode 100644 index 00000000000000..1371fbe73ef845 --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/palette_panel_container.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './palette_panel_container.scss'; + +import React, { useState, useEffect, MutableRefObject } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiTitle, + EuiButtonIcon, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFocusTrap, + EuiOutsideClickDetector, + EuiPortal, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +export function PalettePanelContainer({ + isOpen, + handleClose, + children, + siblingRef, +}: { + isOpen: boolean; + handleClose: () => void; + children: React.ReactElement | React.ReactElement[]; + siblingRef: MutableRefObject; +}) { + const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); + + const closeFlyout = () => { + handleClose(); + setFocusTrapIsEnabled(false); + }; + + useEffect(() => { + if (isOpen) { + // without setTimeout here the flyout pushes content when animating + setTimeout(() => { + setFocusTrapIsEnabled(true); + }, 255); + } + }, [isOpen]); + + return isOpen && siblingRef.current ? ( + + + +
+ + + + + + + +

+ + {i18n.translate('xpack.lens.table.palettePanelTitle', { + defaultMessage: 'Edit color', + })} + +

+
+
+
+
+ + {children} + + + + {i18n.translate('xpack.lens.table.palettePanelContainer.back', { + defaultMessage: 'Back', + })} + + +
+
+
+
+ ) : null; +} diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/shared_utils.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/shared_utils.tsx new file mode 100644 index 00000000000000..92a949e65c67ea --- /dev/null +++ b/x-pack/plugins/lens/public/datatable_visualization/components/shared_utils.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Datatable } from 'src/plugins/expressions'; +import { getOriginalId } from '../transpose_helpers'; + +export const findMinMaxByColumnId = (columnIds: string[], table: Datatable | undefined) => { + const minMax: Record = {}; + + if (table != null) { + for (const columnId of columnIds) { + const originalId = getOriginalId(columnId); + minMax[originalId] = minMax[originalId] || { max: -Infinity, min: Infinity }; + table.rows.forEach((row) => { + const rowValue = row[columnId]; + if (rowValue != null) { + if (minMax[originalId].min > rowValue) { + minMax[originalId].min = rowValue; + } + if (minMax[originalId].max < rowValue) { + minMax[originalId].max = rowValue; + } + } + }); + // what happens when there's no data in the table? Fallback to a percent range + if (minMax[originalId].max === -Infinity) { + minMax[originalId] = { max: 100, min: 0, fallback: true }; + } + } + } + return minMax; +}; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 22577e8ef5fd31..509969c2b71ec0 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -15,6 +15,8 @@ import { LensIconChartDatatable } from '../../assets/chart_datatable'; import { DataContext, DatatableComponent } from './table_basic'; import { LensMultiTable } from '../../types'; import { DatatableProps } from '../expression'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { IUiSettingsClient } from 'kibana/public'; function sampleArgs() { const indexPatternId = 'indexPatternId'; @@ -99,6 +101,8 @@ describe('DatatableComponent', () => { formatFactory={(x) => x as IFieldFormat} dispatchEvent={onDispatchEvent} getType={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} renderMode="edit" /> ) @@ -118,6 +122,8 @@ describe('DatatableComponent', () => { getType={jest.fn()} rowHasRowClickTriggerActions={[true, true, true]} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ) ).toMatchSnapshot(); @@ -136,6 +142,8 @@ describe('DatatableComponent', () => { getType={jest.fn()} rowHasRowClickTriggerActions={[false, false, false]} renderMode="display" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ) ).toMatchSnapshot(); @@ -158,6 +166,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -199,6 +209,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -279,6 +291,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -325,6 +339,8 @@ describe('DatatableComponent', () => { type === 'count' ? ({ type: 'metrics' } as IAggType) : ({ type: 'buckets' } as IAggType) )} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); expect(component.find(EmptyPlaceholder).prop('icon')).toEqual(LensIconChartDatatable); @@ -345,6 +361,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -393,6 +411,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} renderMode="display" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -421,6 +441,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} renderMode="display" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -447,6 +469,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} renderMode="display" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); @@ -471,6 +495,8 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} /> ); // mnake a copy of the data, changing only the name of the first column @@ -483,4 +509,34 @@ describe('DatatableComponent', () => { 'new a' ); }); + + test('it does compute minMax for each numeric column', () => { + const { data, args } = sampleArgs(); + + const wrapper = shallow( + ({ convert: (x) => x } as IFieldFormat)} + dispatchEvent={onDispatchEvent} + getType={jest.fn()} + renderMode="display" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + /> + ); + + expect(wrapper.find(DataContext.Provider).prop('value').minMaxByColumnId).toEqual({ + c: { min: 3, max: 3 }, + }); + }); }); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index 24cde07cebaa0e..e6fcf3f321f7f3 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -18,6 +18,7 @@ import { EuiDataGridSorting, EuiDataGridStyle, } from '@elastic/eui'; +import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common'; import { FormatFactory, LensFilterEvent, LensTableRowContextMenuEvent } from '../../types'; import { VisualizationContainer } from '../../visualization_container'; import { EmptyPlaceholder } from '../../shared_components'; @@ -40,6 +41,8 @@ import { createGridSortingConfig, createTransposeColumnFilterHandler, } from './table_actions'; +import { findMinMaxByColumnId } from './shared_utils'; +import { CUSTOM_PALETTE } from '../../shared_components/coloring/constants'; export const DataContext = React.createContext({}); @@ -50,8 +53,9 @@ const gridStyle: EuiDataGridStyle = { export interface ColumnConfig { columns: Array< - ColumnState & { + Omit & { type: 'lens_datatable_column'; + palette?: PaletteOutput; } >; sortingColumnId: string | undefined; @@ -203,20 +207,34 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ] ); + const isNumericMap: Record = useMemo(() => { + const numericMap: Record = {}; + for (const column of firstLocalTable.columns) { + numericMap[column.id] = column.meta.type === 'number'; + } + return numericMap; + }, [firstLocalTable]); + const alignments: Record = useMemo(() => { const alignmentMap: Record = {}; columnConfig.columns.forEach((column) => { if (column.alignment) { alignmentMap[column.columnId] = column.alignment; } else { - const isNumeric = - firstLocalTable.columns.find((dataColumn) => dataColumn.id === column.columnId)?.meta - .type === 'number'; - alignmentMap[column.columnId] = isNumeric ? 'right' : 'left'; + alignmentMap[column.columnId] = isNumericMap[column.columnId] ? 'right' : 'left'; } }); return alignmentMap; - }, [firstLocalTable, columnConfig]); + }, [columnConfig, isNumericMap]); + + const minMaxByColumnId: Record = useMemo(() => { + return findMinMaxByColumnId( + columnConfig.columns + .filter(({ columnId }) => isNumericMap[columnId]) + .map(({ columnId }) => columnId), + firstTable + ); + }, [firstTable, isNumericMap, columnConfig]); const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) { @@ -254,7 +272,10 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ]; }, [firstTableRef, onRowContextMenuClick, columnConfig, hasAtLeastOneRowClickAction]); - const renderCellValue = useMemo(() => createGridCell(formatters, DataContext), [formatters]); + const renderCellValue = useMemo( + () => createGridCell(formatters, columnConfig, DataContext, props.uiSettings), + [formatters, columnConfig, props.uiSettings] + ); const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns: () => {} }), [ visibleColumns, @@ -286,6 +307,8 @@ export const DatatableComponent = (props: DatatableRenderProps) => { table: firstLocalTable, rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions, alignments, + minMaxByColumnId, + getColorForValue: props.paletteService.get(CUSTOM_PALETTE).getColorForValue!, }} > IAggType; renderMode: RenderMode; + paletteService: PaletteRegistry; + uiSettings: IUiSettingsClient; /** * A boolean for each table row, which is true if the row active @@ -55,4 +59,10 @@ export interface DataContextType { table?: Datatable; rowHasRowClickTriggerActions?: boolean[]; alignments?: Record; + minMaxByColumnId?: Record; + getColorForValue?: ( + value: number | undefined, + state: CustomPaletteState, + minMax: { min: number; max: number } + ) => string | undefined; } diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 7d879217abf8b2..2d5f4aea988562 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -17,6 +17,9 @@ import { ExpressionFunctionDefinition, ExpressionRenderDefinition, } from 'src/plugins/expressions'; +import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common'; +import { PaletteRegistry } from 'src/plugins/charts/public'; +import { IUiSettingsClient } from 'kibana/public'; import { getSortingCriteria } from './sorting'; import { DatatableComponent } from './components/table_basic'; @@ -26,10 +29,15 @@ import type { FormatFactory, ILensInterpreterRenderHandlers, LensMultiTable } fr import type { DatatableRender } from './components/types'; import { transposeTable } from './transpose_helpers'; +export type ColumnConfigArg = Omit & { + type: 'lens_datatable_column'; + palette?: PaletteOutput; +}; + export interface Args { title: string; description?: string; - columns: Array; + columns: ColumnConfigArg[]; sortingColumnId: string | undefined; sortingDirection: 'asc' | 'desc' | 'none'; } @@ -160,6 +168,11 @@ export const datatableColumn: ExpressionFunctionDefinition< width: { types: ['number'], help: '' }, isTransposed: { types: ['boolean'], help: '' }, transposable: { types: ['boolean'], help: '' }, + colorMode: { types: ['string'], help: '' }, + palette: { + types: ['palette'], + help: '', + }, }, fn: function fn(input: unknown, args: ColumnState) { return { @@ -172,6 +185,8 @@ export const datatableColumn: ExpressionFunctionDefinition< export const getDatatableRenderer = (dependencies: { formatFactory: FormatFactory; getType: Promise<(name: string) => IAggType>; + paletteService: PaletteRegistry; + uiSettings: IUiSettingsClient; }): ExpressionRenderDefinition => ({ name: 'lens_datatable_renderer', displayName: i18n.translate('xpack.lens.datatable.visualizationName', { @@ -222,8 +237,10 @@ export const getDatatableRenderer = (dependencies: { formatFactory={dependencies.formatFactory} dispatchEvent={handlers.event} renderMode={handlers.getRenderMode()} + paletteService={dependencies.paletteService} getType={resolvedGetType} rowHasRowClickTriggerActions={rowHasRowClickTriggerActions} + uiSettings={dependencies.uiSettings} /> , domNode, diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts index f0939f61952294..7f48d00d00f7f7 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/index.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts @@ -6,6 +6,7 @@ */ import { CoreSetup } from 'kibana/public'; +import { ChartsPluginSetup } from 'src/plugins/charts/public'; import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { EditorFrameSetup, FormatFactory } from '../types'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; @@ -17,6 +18,7 @@ export interface DatatableVisualizationPluginSetupPlugins { expressions: ExpressionsSetup; formatFactory: Promise; editorFrame: EditorFrameSetup; + charts: ChartsPluginSetup; } export class DatatableVisualization { @@ -24,15 +26,16 @@ export class DatatableVisualization { setup( core: CoreSetup, - { expressions, formatFactory, editorFrame }: DatatableVisualizationPluginSetupPlugins + { expressions, formatFactory, editorFrame, charts }: DatatableVisualizationPluginSetupPlugins ) { editorFrame.registerVisualization(async () => { const { getDatatable, datatableColumn, getDatatableRenderer, - datatableVisualization, + getDatatableVisualization, } = await import('../async_services'); + const palettes = await charts.palettes.getPalettes(); const resolvedFormatFactory = await formatFactory; expressions.registerFunction(() => datatableColumn); @@ -43,9 +46,11 @@ export class DatatableVisualization { getType: core .getStartServices() .then(([_, { data: dataStart }]) => dataStart.search.aggs.types.get), + paletteService: palettes, + uiSettings: core.uiSettings, }) ); - return datatableVisualization; + return getDatatableVisualization({ paletteService: palettes }); }); } } diff --git a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts index 6e29e018b481e2..a35edf7499073a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts @@ -7,9 +7,9 @@ import type { FieldFormat } from 'src/plugins/data/public'; import type { Datatable, DatatableColumn, DatatableRow } from 'src/plugins/expressions'; +import { ColumnConfig } from './components/table_basic'; -import { Args } from './expression'; -import { ColumnState } from './visualization'; +import { Args, ColumnConfigArg } from './expression'; const TRANSPOSE_SEPARATOR = '---'; @@ -87,11 +87,11 @@ export function transposeTable( function transposeRows( firstTable: Datatable, - bucketsColumnArgs: Array, + bucketsColumnArgs: ColumnConfigArg[], formatters: Record, transposedColumnFormatter: FieldFormat, transposedColumnId: string, - metricsColumnArgs: Array + metricsColumnArgs: ColumnConfigArg[] ) { const rowsByBucketColumns: Record = groupRowsByBucketColumns( firstTable, @@ -113,8 +113,8 @@ function transposeRows( */ function updateColumnArgs( args: Args, - bucketsColumnArgs: Array, - transposedColumnGroups: Array> + bucketsColumnArgs: ColumnConfig['columns'], + transposedColumnGroups: Array ) { args.columns = [...bucketsColumnArgs]; // add first column from each group, then add second column for each group, ... @@ -151,8 +151,8 @@ function getUniqueValues(table: Datatable, formatter: FieldFormat, columnId: str */ function transposeColumns( args: Args, - bucketsColumnArgs: Array, - metricColumns: Array, + bucketsColumnArgs: ColumnConfig['columns'], + metricColumns: ColumnConfig['columns'], firstTable: Datatable, uniqueValues: string[], uniqueRawValues: unknown[], @@ -196,10 +196,10 @@ function transposeColumns( */ function mergeRowGroups( rowsByBucketColumns: Record, - bucketColumns: ColumnState[], + bucketColumns: ColumnConfigArg[], formatter: FieldFormat, transposedColumnId: string, - metricColumns: ColumnState[] + metricColumns: ColumnConfigArg[] ) { return Object.values(rowsByBucketColumns).map((rows) => { const mergedRow: DatatableRow = {}; @@ -222,7 +222,7 @@ function mergeRowGroups( */ function groupRowsByBucketColumns( firstTable: Datatable, - bucketColumns: ColumnState[], + bucketColumns: ColumnConfigArg[], formatters: Record ) { const rowsByBucketColumns: Record = {}; diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx index 1848565114dea5..ea8237defc2911 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.test.tsx @@ -8,7 +8,7 @@ import { Ast } from '@kbn/interpreter/common'; import { buildExpression } from '../../../../../src/plugins/expressions/public'; import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks'; -import { DatatableVisualizationState, datatableVisualization } from './visualization'; +import { DatatableVisualizationState, getDatatableVisualization } from './visualization'; import { Operation, DataType, @@ -16,6 +16,7 @@ import { TableSuggestionColumn, VisualizationDimensionGroupConfig, } from '../types'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; function mockFrame(): FramePublicAPI { return { @@ -32,6 +33,10 @@ function mockFrame(): FramePublicAPI { }; } +const datatableVisualization = getDatatableVisualization({ + paletteService: chartPluginMock.createPaletteRegistry(), +}); + describe('Datatable Visualization', () => { describe('#initialize', () => { it('should initialize from the empty state', () => { @@ -427,22 +432,28 @@ describe('Datatable Visualization', () => { ); const columnArgs = buildExpression(expression).findFunction('lens_datatable_column'); expect(columnArgs).toHaveLength(2); - expect(columnArgs[0].arguments).toEqual({ - columnId: ['c'], - hidden: [], - width: [], - isTransposed: [], - transposable: [true], - alignment: [], - }); - expect(columnArgs[1].arguments).toEqual({ - columnId: ['b'], - hidden: [], - width: [], - isTransposed: [], - transposable: [true], - alignment: [], - }); + expect(columnArgs[0].arguments).toEqual( + expect.objectContaining({ + columnId: ['c'], + hidden: [], + width: [], + isTransposed: [], + transposable: [true], + alignment: [], + colorMode: ['none'], + }) + ); + expect(columnArgs[1].arguments).toEqual( + expect.objectContaining({ + columnId: ['b'], + hidden: [], + width: [], + isTransposed: [], + transposable: [true], + alignment: [], + colorMode: ['none'], + }) + ); }); it('returns no expression if the metric dimension is not defined', () => { diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 9bd482c73bff58..efde4160019e73 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -11,6 +11,7 @@ import { Ast } from '@kbn/interpreter/common'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { DatatableColumn } from 'src/plugins/expressions/public'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { SuggestionRequest, Visualization, @@ -19,6 +20,9 @@ import { } from '../types'; import { LensIconChartDatatable } from '../assets/chart_datatable'; import { TableDimensionEditor } from './components/dimension_editor'; +import { CUSTOM_PALETTE } from '../shared_components/coloring/constants'; +import { CustomPaletteParams } from '../shared_components/coloring/types'; +import { getStopsForFixedMode } from '../shared_components'; export interface ColumnState { columnId: string; @@ -32,6 +36,8 @@ export interface ColumnState { originalName?: string; bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>; alignment?: 'left' | 'right' | 'center'; + palette?: PaletteOutput; + colorMode?: 'none' | 'cell' | 'text'; } export interface SortingState { @@ -49,7 +55,11 @@ const visualizationLabel = i18n.translate('xpack.lens.datatable.label', { defaultMessage: 'Table', }); -export const datatableVisualization: Visualization = { +export const getDatatableVisualization = ({ + paletteService, +}: { + paletteService: PaletteRegistry; +}): Visualization => ({ id: 'lnsDatatable', visualizationTypes: [ @@ -239,10 +249,26 @@ export const datatableVisualization: Visualization layerId: state.layerId, accessors: sortedColumns .filter((c) => !datasource!.getOperationForColumnId(c)?.isBucketed) - .map((accessor) => ({ - columnId: accessor, - triggerIcon: columnMap[accessor].hidden ? 'invisible' : undefined, - })), + .map((accessor) => { + const columnConfig = columnMap[accessor]; + const hasColoring = Boolean( + columnConfig.colorMode !== 'none' && columnConfig.palette?.params?.stops + ); + return { + columnId: accessor, + triggerIcon: columnConfig.hidden + ? 'invisible' + : hasColoring + ? 'colorBy' + : undefined, + palette: hasColoring + ? getStopsForFixedMode( + columnConfig.palette?.params?.stops || [], + columnConfig.palette?.params?.colorStops + ) + : undefined, + }; + }), supportsMoreColumns: true, filterOperations: (op) => !op.isBucketed, required: true, @@ -285,7 +311,7 @@ export const datatableVisualization: Visualization renderDimensionEditor(domElement, props) { render( - + , domElement ); @@ -320,26 +346,41 @@ export const datatableVisualization: Visualization arguments: { title: [title || ''], description: [description || ''], - columns: columns.map((column) => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'lens_datatable_column', - arguments: { - columnId: [column.columnId], - hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], - width: typeof column.width === 'undefined' ? [] : [column.width], - isTransposed: - typeof column.isTransposed === 'undefined' ? [] : [column.isTransposed], - transposable: [ - !datasource!.getOperationForColumnId(column.columnId)?.isBucketed, - ], - alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], + columns: columns.map((column) => { + const paletteParams = { + ...column.palette?.params, + // rewrite colors and stops as two distinct arguments + colors: (column.palette?.params?.stops || []).map(({ color }) => color), + stops: + column.palette?.params?.name === 'custom' + ? (column.palette?.params?.stops || []).map(({ stop }) => stop) + : [], + reverse: false, // managed at UI level + }; + + return { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_datatable_column', + arguments: { + columnId: [column.columnId], + hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden], + width: typeof column.width === 'undefined' ? [] : [column.width], + isTransposed: + typeof column.isTransposed === 'undefined' ? [] : [column.isTransposed], + transposable: [ + !datasource!.getOperationForColumnId(column.columnId)?.isBucketed, + ], + alignment: typeof column.alignment === 'undefined' ? [] : [column.alignment], + colorMode: [column.colorMode ?? 'none'], + palette: [paletteService.get(CUSTOM_PALETTE).toExpression(paletteParams)], + }, }, - }, - ], - })), + ], + }; + }), sortingColumnId: [state.sorting?.columnId || ''], sortingDirection: [state.sorting?.direction || 'none'], }, @@ -395,7 +436,7 @@ export const datatableVisualization: Visualization return state; } }, -}; +}); function getDataSourceAndSortedColumns( state: DatatableVisualizationState, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx index b8d3170b3e1650..a8d610f2740de5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -29,11 +29,13 @@ export function DimensionContainer({ groupLabel, handleClose, panel, + panelRef, }: { isOpen: boolean; handleClose: () => void; panel: React.ReactElement; groupLabel: string; + panelRef: (el: HTMLDivElement) => void; }) { const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); @@ -73,65 +75,67 @@ export function DimensionContainer({ }); return isOpen ? ( - - - -
- - - - - - - -

- - {i18n.translate('xpack.lens.configure.configurePanelTitle', { - defaultMessage: '{groupLabel} configuration', - values: { - groupLabel, - }, - })} - -

-
-
-
-
- - {panel} - - - - {i18n.translate('xpack.lens.dimensionContainer.close', { - defaultMessage: 'Close', - })} - - -
-
-
+
+ + + +
+ + + + +

+ + {i18n.translate('xpack.lens.configure.configurePanelTitle', { + defaultMessage: '{groupLabel} configuration', + values: { + groupLabel, + }, + })} + +

+
+
+ + + +
+
+ + {panel} + + + + {i18n.translate('xpack.lens.dimensionContainer.close', { + defaultMessage: 'Close', + })} + + +
+
+
+
) : null; } 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 cf3c9099d4b0dd..a605a94a346468 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 @@ -7,7 +7,7 @@ import './layer_panel.scss'; -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import { EuiPanel, EuiSpacer, @@ -72,6 +72,7 @@ export function LayerPanel( setActiveDimension(initialActiveDimensionState); }, [activeVisualization.id]); + const panelRef = useRef(null); const registerLayerRef = useCallback((el) => registerNewLayerRef(layerId, el), [ layerId, registerNewLayerRef, @@ -405,6 +406,7 @@ export function LayerPanel( (panelRef.current = el)} isOpen={!!activeId} groupLabel={activeGroup?.groupLabel || ''} handleClose={() => { @@ -484,6 +486,7 @@ export function LayerPanel( groupId: activeGroup.groupId, accessor: activeId, setState: props.updateVisualization, + panelRef, }} />
diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index a56b3ccaa5bde7..38669d72474df1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -105,10 +105,9 @@ export type FrameMock = jest.Mocked; export function createMockPaletteDefinition(): jest.Mocked { return { - getColors: jest.fn((_) => ['#ff0000', '#00ff00']), + getCategoricalColors: jest.fn((_) => ['#ff0000', '#00ff00']), title: 'Mock Palette', id: 'default', - renderEditor: jest.fn(), toExpression: jest.fn(() => ({ type: 'expression', chain: [ @@ -119,7 +118,7 @@ export function createMockPaletteDefinition(): jest.Mocked { }, ], })), - getColor: jest.fn().mockReturnValue('#ff0000'), + getCategoricalColor: jest.fn().mockReturnValue('#ff0000'), }; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index eeec7871a262c7..03eb234d907660 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -237,7 +237,7 @@ const initialState: IndexPatternPrivateState = { isFirstExistenceFetch: false, }; -const dslQuery = { bool: { must: [{ match_all: {} }], filter: [], should: [], must_not: [] } }; +const dslQuery = { bool: { must: [], filter: [], should: [], must_not: [] } }; describe('IndexPattern Data Panel', () => { let defaultProps: Parameters[0] & { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index cf9f7c0c559e42..2aa031959f5d7e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -164,7 +164,7 @@ describe('IndexPattern Field Item', () => { body: JSON.stringify({ dslQuery: { bool: { - must: [{ match_all: {} }], + must: [], filter: [], should: [], must_not: [], diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index 8d18a2752fd7e3..0e74ef6b85c804 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import { useDebounceWithOptions } from '../../../../shared_components'; import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types'; import { IndexPatternLayer } from '../../../types'; import { @@ -19,12 +20,7 @@ import { hasDateField, } from './utils'; import { updateColumnParam } from '../../layer_helpers'; -import { - getFormatFromPreviousColumn, - isValidNumber, - useDebounceWithOptions, - getFilter, -} from '../helpers'; +import { getFormatFromPreviousColumn, isValidNumber, getFilter } from '../helpers'; import { adjustTimeScaleOnOtherColumnChange } from '../../time_scale_utils'; import { HelpPopover, HelpPopoverButton } from '../../../help_popover'; import type { OperationDefinition, ParamEditorProps } from '..'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index f719ac4250912e..45abbcd3d9cf96 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -5,35 +5,11 @@ * 2.0. */ -import { useRef } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; import { IndexPatternColumn, operationDefinitionMap } from '.'; import { FieldBasedIndexPatternColumn } from './column_types'; import { IndexPattern } from '../../types'; -export const useDebounceWithOptions = ( - fn: Function, - { skipFirstRender }: { skipFirstRender: boolean } = { skipFirstRender: false }, - ms?: number | undefined, - deps?: React.DependencyList | undefined -) => { - const isFirstRender = useRef(true); - const newDeps = [...(deps || []), isFirstRender]; - - return useDebounce( - () => { - if (skipFirstRender && isFirstRender.current) { - isFirstRender.current = false; - return; - } - return fn(); - }, - ms, - newDeps - ); -}; - export function getInvalidFieldMessage( column: FieldBasedIndexPatternColumn, indexPattern?: IndexPattern diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index 705a1f7172fff8..4c09ae4ed8c47b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -16,10 +16,10 @@ import { getInvalidFieldMessage, getSafeName, isValidNumber, - useDebounceWithOptions, getFilter, } from './helpers'; import { FieldBasedIndexPatternColumn } from './column_types'; +import { useDebounceWithOptions } from '../../../shared_components'; export interface PercentileIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: 'percentile'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index b3ffb58df00d39..43f5527e42d4bf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -22,6 +22,7 @@ import { htmlIdGenerator, keys, } from '@elastic/eui'; +import { useDebounceWithOptions } from '../../../../shared_components'; import { IFieldFormat } from '../../../../../../../../src/plugins/data/common'; import { RangeTypeLens, isValidRange } from './ranges'; import { FROM_PLACEHOLDER, TO_PLACEHOLDER, TYPING_DEBOUNCE_TIME } from './constants'; @@ -31,7 +32,7 @@ import { DraggableBucketContainer, LabelInput, } from '../shared_components'; -import { isValidNumber, useDebounceWithOptions } from '../helpers'; +import { isValidNumber } from '../helpers'; const generateId = htmlIdGenerator(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx index 4851b6ff3ec975..3389c723b4daf6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -23,7 +23,7 @@ import { UI_SETTINGS } from '../../../../../../../../src/plugins/data/public'; import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges'; import { AdvancedRangeEditor } from './advanced_editor'; import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants'; -import { useDebounceWithOptions } from '../helpers'; +import { useDebounceWithOptions } from '../../../../shared_components'; import { HelpPopover, HelpPopoverButton } from '../../../help_popover'; const GranularityHelpPopover = () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx index 915e67c4eba0bd..a4c0f8f1c50e05 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFieldNumber } from '@elastic/eui'; -import { useDebounceWithOptions } from '../helpers'; +import { useDebounceWithOptions } from '../../../../shared_components'; export const ValuesInput = ({ value, diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index 7191da0af6bfe7..a9e7e4adb9ca78 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -161,7 +161,7 @@ describe('PieVisualization component', () => { [] as HierarchyOfArrays ); - expect(defaultArgs.paletteService.get('mock').getColor).toHaveBeenCalledWith( + expect(defaultArgs.paletteService.get('mock').getCategoricalColor).toHaveBeenCalledWith( [ { name: 'css', diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index cc31222f6b9ab0..6c1cbe63a5a3e3 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -150,7 +150,7 @@ export function PieComponent( } } - const outputColor = paletteService.get(palette.name).getColor( + const outputColor = paletteService.get(palette.name).getCategoricalColor( seriesLayers, { behindText: categoryDisplay !== 'hide', diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx index ad8d87292b1d80..f413b122d913cc 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx @@ -126,7 +126,7 @@ export const getPieVisualization = ({ triggerIcon: 'colorBy', palette: paletteService .get(state.palette?.name || 'default') - .getColors(10, state.palette?.params), + .getCategoricalColors(10, state.palette?.params), }; } diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx new file mode 100644 index 00000000000000..54c7f3cef90fe3 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiColorPicker } from '@elastic/eui'; +import { mount } from 'enzyme'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { CustomStops, CustomStopsProps } from './color_stops'; + +describe('Color Stops component', () => { + let props: CustomStopsProps; + beforeEach(() => { + props = { + colorStops: [ + { color: '#aaa', stop: 20 }, + { color: '#bbb', stop: 40 }, + { color: '#ccc', stop: 60 }, + ], + paletteConfiguration: {}, + dataBounds: { min: 0, max: 200 }, + onChange: jest.fn(), + 'data-test-prefix': 'my-test', + }; + }); + it('should display all the color stops passed', () => { + const component = mount(); + expect( + component.find('input[data-test-subj^="my-test_dynamicColoring_stop_value_"]') + ).toHaveLength(3); + }); + + it('should disable the delete buttons when there are 2 stops or less', () => { + // reduce to 2 stops + props.colorStops = props.colorStops.slice(0, 2); + const component = mount(); + expect( + component + .find('[data-test-subj="my-test_dynamicColoring_removeStop_0"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + it('should add a new stop with default color and reasonable distance from last one', () => { + let component = mount(); + const addStopButton = component + .find('[data-test-subj="my-test_dynamicColoring_addStop"]') + .first(); + act(() => { + addStopButton.prop('onClick')!({} as React.MouseEvent); + }); + component = component.update(); + + expect( + component.find('input[data-test-subj^="my-test_dynamicColoring_stop_value_"]') + ).toHaveLength(4); + expect( + component.find('input[data-test-subj="my-test_dynamicColoring_stop_value_3"]').prop('value') + ).toBe('80'); // 60-40 + 60 + expect( + component + // workaround for https://github.com/elastic/eui/issues/4792 + .find('[data-test-subj="my-test_dynamicColoring_stop_color_3"]') + .last() // pick the inner element + .childAt(0) + .prop('color') + ).toBe('#ccc'); // pick previous color + }); + + it('should restore previous color when abandoning the field with an empty color', () => { + let component = mount(); + expect( + component + .find('[data-test-subj="my-test_dynamicColoring_stop_row_0"]') + .first() + .find(EuiColorPicker) + .first() + .prop('color') + ).toBe('#aaa'); + act(() => { + component + .find('[data-test-subj="my-test_dynamicColoring_stop_row_0"]') + .first() + .find(EuiColorPicker) + .first() + .prop('onChange')!('', { + rgba: [NaN, NaN, NaN, NaN], + hex: '', + isValid: false, + }); + }); + component = component.update(); + expect( + component + .find('[data-test-subj="my-test_dynamicColoring_stop_row_0"]') + .first() + .find(EuiColorPicker) + .first() + .prop('color') + ).toBe(''); + act(() => { + component + .find('[data-test-subj="my-test_dynamicColoring_stop_color_0"]') + .first() + .prop('onBlur')!({} as React.FocusEvent); + }); + component = component.update(); + expect( + component + .find('[data-test-subj="my-test_dynamicColoring_stop_row_0"]') + .first() + .find(EuiColorPicker) + .first() + .prop('color') + ).toBe('#aaa'); + }); + + it('should sort stops value on whole component blur', () => { + let component = mount(); + let firstStopValueInput = component + .find('[data-test-subj="my-test_dynamicColoring_stop_value_0"]') + .first(); + act(() => { + firstStopValueInput.prop('onChange')!(({ + target: { value: ' 90' }, + } as unknown) as React.ChangeEvent); + }); + + component = component.update(); + + act(() => { + component + .find('[data-test-subj="my-test_dynamicColoring_stop_row_0"]') + .first() + .prop('onBlur')!({} as React.FocusEvent); + }); + component = component.update(); + + // retrieve again the input + firstStopValueInput = component + .find('[data-test-subj="my-test_dynamicColoring_stop_value_0"]') + .first(); + expect(firstStopValueInput.prop('value')).toBe('40'); + // the previous one move at the bottom + expect( + component + .find('[data-test-subj="my-test_dynamicColoring_stop_value_2"]') + .first() + .prop('value') + ).toBe('90'); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx new file mode 100644 index 00000000000000..37197b232ddf55 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx @@ -0,0 +1,294 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState, useCallback, useMemo } from 'react'; +import type { FocusEvent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFieldNumber, + EuiColorPicker, + EuiButtonIcon, + EuiFlexItem, + EuiFlexGroup, + EuiButtonEmpty, + EuiSpacer, + EuiScreenReaderOnly, + htmlIdGenerator, +} from '@elastic/eui'; +import useUnmount from 'react-use/lib/useUnmount'; +import { DEFAULT_COLOR } from './constants'; +import { getDataMinMax, getStepValue, isValidColor } from './utils'; +import { TooltipWrapper, useDebouncedValue } from '../index'; +import { ColorStop, CustomPaletteParams } from './types'; + +const idGeneratorFn = htmlIdGenerator(); + +function areStopsValid(colorStops: Array<{ color: string; stop: string }>) { + return colorStops.every( + ({ color, stop }) => isValidColor(color) && !Number.isNaN(parseFloat(stop)) + ); +} + +function shouldSortStops(colorStops: Array<{ color: string; stop: string | number }>) { + return colorStops.some(({ stop }, i) => { + const numberStop = Number(stop); + const prevNumberStop = Number(colorStops[i - 1]?.stop ?? -Infinity); + return numberStop < prevNumberStop; + }); +} + +export interface CustomStopsProps { + colorStops: ColorStop[]; + onChange: (colorStops: ColorStop[]) => void; + dataBounds: { min: number; max: number }; + paletteConfiguration: CustomPaletteParams | undefined; + 'data-test-prefix': string; +} +export const CustomStops = ({ + colorStops, + onChange, + paletteConfiguration, + dataBounds, + ['data-test-prefix']: dataTestPrefix, +}: CustomStopsProps) => { + const onChangeWithValidation = useCallback( + (newColorStops: Array<{ color: string; stop: string }>) => { + const areStopsValuesValid = areStopsValid(newColorStops); + const shouldSort = shouldSortStops(newColorStops); + if (areStopsValuesValid && !shouldSort) { + onChange(newColorStops.map(({ color, stop }) => ({ color, stop: Number(stop) }))); + } + }, + [onChange] + ); + + const memoizedValues = useMemo(() => { + return colorStops.map(({ color, stop }, i) => ({ + color, + stop: String(stop), + id: idGeneratorFn(), + })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [paletteConfiguration?.name, paletteConfiguration?.reverse, paletteConfiguration?.rangeType]); + + const { inputValue: localColorStops, handleInputChange: setLocalColorStops } = useDebouncedValue({ + onChange: onChangeWithValidation, + value: memoizedValues, + }); + const [sortedReason, setSortReason] = useState(''); + const shouldEnableDelete = localColorStops.length > 2; + + const [popoverInFocus, setPopoverInFocus] = useState(false); + + // refresh on unmount: + // the onChange logic here is a bit different than the one above as it has to actively sort if required + useUnmount(() => { + const areStopsValuesValid = areStopsValid(localColorStops); + const shouldSort = shouldSortStops(localColorStops); + if (areStopsValuesValid && shouldSort) { + onChange( + localColorStops + .map(({ color, stop }) => ({ color, stop: Number(stop) })) + .sort(({ stop: stopA }, { stop: stopB }) => Number(stopA) - Number(stopB)) + ); + } + }); + + const rangeType = paletteConfiguration?.rangeType || 'percent'; + + return ( + <> + {sortedReason ? ( + +

+ {i18n.translate('xpack.lens.dynamicColoring.customPalette.sortReason', { + defaultMessage: 'Color stops have been sorted due to new stop value {value}', + values: { + value: sortedReason, + }, + })} +

+
+ ) : null} + + + {localColorStops.map(({ color, stop, id }, index) => { + const prevStopValue = Number(localColorStops[index - 1]?.stop ?? -Infinity); + const nextStopValue = Number(localColorStops[index + 1]?.stop ?? Infinity); + + return ( + ) => { + // sort the stops when the focus leaves the row container + const shouldSort = Number(stop) > nextStopValue || prevStopValue > Number(stop); + const isFocusStillInContent = + (e.currentTarget as Node)?.contains(e.relatedTarget as Node) || popoverInFocus; + const hasInvalidColor = !isValidColor(color); + if ((shouldSort && !isFocusStillInContent) || hasInvalidColor) { + // replace invalid color with previous valid one + const lastValidColor = hasInvalidColor ? colorStops[index].color : color; + const localColorStopsCopy = localColorStops.map((item, i) => + i === index ? { color: lastValidColor, stop, id } : item + ); + setLocalColorStops( + localColorStopsCopy.sort( + ({ stop: stopA }, { stop: stopB }) => Number(stopA) - Number(stopB) + ) + ); + setSortReason(stop); + } + }} + > + + + { + const newStopString = target.value.trim(); + const newColorStops = [...localColorStops]; + newColorStops[index] = { + color, + stop: newStopString, + id, + }; + setLocalColorStops(newColorStops); + }} + append={rangeType === 'percent' ? '%' : undefined} + aria-label={i18n.translate( + 'xpack.lens.dynamicColoring.customPalette.stopAriaLabel', + { + defaultMessage: 'Stop {index}', + values: { + index: index + 1, + }, + } + )} + /> + + + { + // make sure that the popover is closed + if (color === '' && !popoverInFocus) { + const newColorStops = [...localColorStops]; + newColorStops[index] = { color: colorStops[index].color, stop, id }; + setLocalColorStops(newColorStops); + } + }} + > + { + const newColorStops = [...localColorStops]; + newColorStops[index] = { color: newColor, stop, id }; + setLocalColorStops(newColorStops); + }} + secondaryInputDisplay="top" + color={color} + isInvalid={!isValidColor(color)} + showAlpha + compressed + onFocus={() => setPopoverInFocus(true)} + onBlur={() => { + setPopoverInFocus(false); + if (color === '') { + const newColorStops = [...localColorStops]; + newColorStops[index] = { color: colorStops[index].color, stop, id }; + setLocalColorStops(newColorStops); + } + }} + placeholder=" " + /> + + + + + { + const newColorStops = localColorStops.filter((_, i) => i !== index); + setLocalColorStops(newColorStops); + }} + data-test-subj={`${dataTestPrefix}_dynamicColoring_removeStop_${index}`} + isDisabled={!shouldEnableDelete} + /> + + + + + ); + })} + + + + + { + const newColorStops = [...localColorStops]; + const length = newColorStops.length; + const { max } = getDataMinMax(rangeType, dataBounds); + const step = getStepValue( + colorStops, + newColorStops.map(({ color, stop }) => ({ color, stop: Number(stop) })), + max + ); + const prevColor = localColorStops[length - 1].color || DEFAULT_COLOR; + const newStop = step + Number(localColorStops[length - 1].stop); + newColorStops.push({ + color: prevColor, + stop: String(newStop), + id: idGeneratorFn(), + }); + setLocalColorStops(newColorStops); + }} + > + {i18n.translate('xpack.lens.dynamicColoring.customPalette.addColorStop', { + defaultMessage: 'Add color stop', + })} + + + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/constants.ts b/x-pack/plugins/lens/public/shared_components/coloring/constants.ts new file mode 100644 index 00000000000000..5e6fc207656ac3 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/constants.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequiredPaletteParamTypes } from './types'; + +export const DEFAULT_PALETTE_NAME = 'positive'; +export const FIXED_PROGRESSION = 'fixed' as const; +export const CUSTOM_PALETTE = 'custom'; +export const DEFAULT_CONTINUITY = 'above'; +export const DEFAULT_MIN_STOP = 0; +export const DEFAULT_MAX_STOP = 100; +export const DEFAULT_COLOR_STEPS = 5; +export const DEFAULT_COLOR = '#6092C0'; // Same as EUI ColorStops default for new stops +export const defaultPaletteParams: RequiredPaletteParamTypes = { + name: DEFAULT_PALETTE_NAME, + reverse: false, + rangeType: 'percent', + rangeMin: DEFAULT_MIN_STOP, + rangeMax: DEFAULT_MAX_STOP, + progression: FIXED_PROGRESSION, + stops: [], + steps: DEFAULT_COLOR_STEPS, + colorStops: [], + continuity: DEFAULT_CONTINUITY, +}; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/index.ts b/x-pack/plugins/lens/public/shared_components/coloring/index.ts new file mode 100644 index 00000000000000..3b34c6662c6819 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { CustomizablePalette } from './palette_configuration'; +export { CustomStops } from './color_stops'; +export * from './types'; +export * from './utils'; +export * from './constants'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.scss b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.scss new file mode 100644 index 00000000000000..c6b14c5c5f9a35 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.scss @@ -0,0 +1,7 @@ +.lnsPalettePanel__section--shaded { + background-color: $euiColorLightestShade; +} + +.lnsPalettePanel__section { + padding: $euiSizeS; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx new file mode 100644 index 00000000000000..28ba28a5801e40 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiColorPalettePickerPaletteProps } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test/jest'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { ReactWrapper } from 'enzyme'; +import { CustomPaletteParams } from './types'; +import { applyPaletteParams } from './utils'; +import { CustomizablePalette } from './palette_configuration'; + +describe('palette utilities', () => { + const paletteRegistry = chartPluginMock.createPaletteRegistry(); + describe('applyPaletteParams', () => { + it('should return a set of colors for a basic configuration', () => { + expect( + applyPaletteParams( + paletteRegistry, + { type: 'palette', name: 'positive' }, + { min: 0, max: 100 } + ) + ).toEqual([ + { color: 'blue', stop: 20 }, + { color: 'yellow', stop: 70 }, + ]); + }); + + it('should reverse the palette color stops correctly', () => { + expect( + applyPaletteParams( + paletteRegistry, + { + type: 'palette', + name: 'positive', + params: { reverse: true }, + }, + { min: 0, max: 100 } + ) + ).toEqual([ + { color: 'yellow', stop: 20 }, + { color: 'blue', stop: 70 }, + ]); + }); + }); +}); + +describe('palette panel', () => { + const paletteRegistry = chartPluginMock.createPaletteRegistry(); + let props: { + palettes: PaletteRegistry; + activePalette: PaletteOutput; + setPalette: (palette: PaletteOutput) => void; + dataBounds: { min: number; max: number }; + }; + + describe('palette picker', () => { + beforeEach(() => { + props = { + activePalette: { type: 'palette', name: 'positive' }, + palettes: paletteRegistry, + setPalette: jest.fn(), + dataBounds: { min: 0, max: 100 }, + }; + }); + + function changePaletteIn(instance: ReactWrapper, newPaletteName: string) { + return ((instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_palette_picker"]') + .at(1) + .prop('onChange') as unknown) as (value: string) => void)?.(newPaletteName); + } + + it('should show only dynamic coloring enabled palette + custom option', () => { + const instance = mountWithIntl(); + const paletteOptions = instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_palette_picker"]') + .at(1) + .prop('palettes') as EuiColorPalettePickerPaletteProps[]; + expect(paletteOptions.length).toEqual(2); + + expect(paletteOptions[paletteOptions.length - 1]).toEqual({ + title: 'Custom Mocked Palette', // <- picks the title of the custom palette + type: 'fixed', + value: 'custom', + palette: ['blue', 'yellow'], + 'data-test-subj': 'custom-palette', + }); + }); + + it('should set the colorStops and stops when selecting the Custom palette from the list', () => { + const instance = mountWithIntl(); + + changePaletteIn(instance, 'custom'); + + expect(props.setPalette).toHaveBeenCalledWith({ + type: 'palette', + name: 'custom', + params: expect.objectContaining({ + colorStops: [ + { color: 'blue', stop: 0 }, + { color: 'yellow', stop: 50 }, + ], + stops: [ + { color: 'blue', stop: 50 }, + { color: 'yellow', stop: 100 }, + ], + name: 'custom', + }), + }); + }); + + describe('reverse option', () => { + beforeEach(() => { + props = { + activePalette: { type: 'palette', name: 'positive' }, + palettes: paletteRegistry, + setPalette: jest.fn(), + dataBounds: { min: 0, max: 100 }, + }; + }); + + function toggleReverse(instance: ReactWrapper, checked: boolean) { + return instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_reverse"]') + .first() + .prop('onClick')!({} as React.MouseEvent); + } + + it('should reverse the colorStops on click', () => { + const instance = mountWithIntl(); + + toggleReverse(instance, true); + + expect(props.setPalette).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + reverse: true, + }), + }) + ); + }); + }); + + describe('custom stops', () => { + beforeEach(() => { + props = { + activePalette: { type: 'palette', name: 'positive' }, + palettes: paletteRegistry, + setPalette: jest.fn(), + dataBounds: { min: 0, max: 100 }, + }; + }); + it('should be visible for predefined palettes', () => { + const instance = mountWithIntl(); + expect( + instance.find('[data-test-subj="lnsDatatable_dynamicColoring_custom_stops"]').exists() + ).toEqual(true); + }); + + it('should be visible for custom palettes', () => { + const instance = mountWithIntl( + + ); + expect( + instance.find('[data-test-subj="lnsDatatable_dynamicColoring_custom_stops"]').exists() + ).toEqual(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx new file mode 100644 index 00000000000000..df01b3e57cd7df --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx @@ -0,0 +1,340 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { + EuiFormRow, + htmlIdGenerator, + EuiButtonGroup, + EuiFlexGroup, + EuiFlexItem, + EuiSuperSelect, + EuiIcon, + EuiIconTip, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { PalettePicker } from './palette_picker'; + +import './palette_configuration.scss'; + +import { CustomStops } from './color_stops'; +import { defaultPaletteParams, CUSTOM_PALETTE, DEFAULT_COLOR_STEPS } from './constants'; +import { CustomPaletteParams, RequiredPaletteParamTypes } from './types'; +import { + getColorStops, + getPaletteStops, + mergePaletteParams, + getDataMinMax, + remapStopsByNewInterval, + getSwitchToCustomParams, + reversePalette, + roundStopValues, +} from './utils'; +const idPrefix = htmlIdGenerator()(); + +/** + * Some name conventions here: + * * `displayStops` => It's an additional transformation of `stops` into a [0, N] domain for the EUIPaletteDisplay component. + * * `stops` => final steps used to table coloring. It is a rightShift of the colorStops + * * `colorStops` => user's color stop inputs. Used to compute range min. + * + * When the user inputs the colorStops, they are designed to be the initial part of the color segment, + * so the next stops indicate where the previous stop ends. + * Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`, + * by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`. + * EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with + * some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok. + * + * These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening + * for a single change. + */ + +export function CustomizablePalette({ + palettes, + activePalette, + setPalette, + dataBounds, +}: { + palettes: PaletteRegistry; + activePalette: PaletteOutput; + setPalette: (palette: PaletteOutput) => void; + dataBounds: { min: number; max: number }; +}) { + const isCurrentPaletteCustom = activePalette.params?.name === CUSTOM_PALETTE; + + const colorStopsToShow = roundStopValues( + getColorStops(palettes, activePalette?.params?.colorStops || [], activePalette, dataBounds) + ); + + return ( + <> +
+ + { + const isNewPaletteCustom = newPalette.name === CUSTOM_PALETTE; + const newParams: CustomPaletteParams = { + ...activePalette.params, + name: newPalette.name, + colorStops: undefined, + }; + + if (isNewPaletteCustom) { + newParams.colorStops = getColorStops(palettes, [], activePalette, dataBounds); + } + + newParams.stops = getPaletteStops(palettes, newParams, { + prevPalette: + isNewPaletteCustom || isCurrentPaletteCustom ? undefined : newPalette.name, + dataBounds, + }); + + setPalette({ + ...newPalette, + params: newParams, + }); + }} + showCustomPalette + showDynamicColorOnly + /> + + + ['continuity']) => + setPalette( + mergePaletteParams(activePalette, { + continuity, + }) + ) + } + /> + + + {i18n.translate('xpack.lens.table.dynamicColoring.rangeType.label', { + defaultMessage: 'Value type', + })}{' '} + + + } + display="rowCompressed" + > + { + const newRangeType = id.replace( + idPrefix, + '' + ) as RequiredPaletteParamTypes['rangeType']; + + const params: CustomPaletteParams = { rangeType: newRangeType }; + if (isCurrentPaletteCustom) { + const { min: newMin, max: newMax } = getDataMinMax(newRangeType, dataBounds); + const { min: oldMin, max: oldMax } = getDataMinMax( + activePalette.params?.rangeType, + dataBounds + ); + const newColorStops = remapStopsByNewInterval(colorStopsToShow, { + oldInterval: oldMax - oldMin, + newInterval: newMax - newMin, + newMin, + oldMin, + }); + const stops = getPaletteStops( + palettes, + { ...activePalette.params, colorStops: newColorStops, ...params }, + { dataBounds } + ); + params.colorStops = newColorStops; + params.stops = stops; + params.rangeMin = newColorStops[0].stop; + params.rangeMax = newColorStops[newColorStops.length - 1].stop; + } else { + params.stops = getPaletteStops( + palettes, + { ...activePalette.params, ...params }, + { prevPalette: activePalette.name, dataBounds } + ); + } + setPalette(mergePaletteParams(activePalette, params)); + }} + /> + + + { + const params: CustomPaletteParams = { reverse: !activePalette.params?.reverse }; + if (isCurrentPaletteCustom) { + params.colorStops = reversePalette(colorStopsToShow); + params.stops = getPaletteStops( + palettes, + { + ...(activePalette?.params || {}), + colorStops: params.colorStops, + }, + { dataBounds } + ); + } else { + params.stops = reversePalette( + activePalette?.params?.stops || + getPaletteStops( + palettes, + { ...activePalette.params, ...params }, + { prevPalette: activePalette.name, dataBounds } + ) + ); + } + setPalette(mergePaletteParams(activePalette, params)); + }} + > + + + + + + {i18n.translate('xpack.lens.table.dynamicColoring.reverse.label', { + defaultMessage: 'Reverse colors', + })} + + + + + } + > + { + const newParams = getSwitchToCustomParams( + palettes, + activePalette, + { + colorStops, + steps: activePalette.params!.steps || DEFAULT_COLOR_STEPS, + rangeMin: colorStops[0]?.stop, + rangeMax: colorStops[colorStops.length - 1]?.stop, + }, + dataBounds + ); + return setPalette(newParams); + }} + /> + +
+ + ); +} diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx new file mode 100644 index 00000000000000..164ed9bf067a66 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { + CUSTOM_PALETTE, + DEFAULT_COLOR_STEPS, + FIXED_PROGRESSION, + defaultPaletteParams, +} from '../../shared_components/coloring/constants'; +import { CustomPaletteParams } from '../../shared_components/coloring/types'; +import { getStopsForFixedMode } from '../../shared_components/coloring/utils'; + +function getCustomPaletteConfig( + palettes: PaletteRegistry, + activePalette: PaletteOutput | undefined +) { + const { id, title } = palettes.get(CUSTOM_PALETTE); + + // Try to generate a palette from the current one + if (activePalette && activePalette.name !== CUSTOM_PALETTE) { + const currentPalette = palettes.get(activePalette.name); + if (currentPalette) { + const stops = currentPalette.getCategoricalColors(DEFAULT_COLOR_STEPS, activePalette?.params); + const palette = activePalette.params?.reverse ? stops.reverse() : stops; + return { + value: id, + title, + type: FIXED_PROGRESSION, + palette, + 'data-test-subj': `custom-palette`, + }; + } + } + // if not possible just show some text + if (!activePalette?.params?.stops) { + return { value: id, title, type: 'text' as const, 'data-test-subj': `custom-palette` }; + } + + // full custom palette + return { + value: id, + title, + type: FIXED_PROGRESSION, + 'data-test-subj': `custom-palette`, + palette: getStopsForFixedMode(activePalette.params.stops, activePalette.params.colorStops), + }; +} + +// Note: this is a special palette picker different from the one in the root shared folder +// ideally these should be merged together, but as for now this holds some custom logic hard to remove +export function PalettePicker({ + palettes, + activePalette, + setPalette, + showCustomPalette, + showDynamicColorOnly, + ...rest +}: { + palettes: PaletteRegistry; + activePalette?: PaletteOutput; + setPalette: (palette: PaletteOutput) => void; + showCustomPalette?: boolean; + showDynamicColorOnly?: boolean; +}) { + const palettesToShow: EuiColorPalettePickerPaletteProps[] = palettes + .getAll() + .filter(({ internal, canDynamicColoring }) => + showDynamicColorOnly ? canDynamicColoring : !internal + ) + .map(({ id, title, getCategoricalColors }) => { + const colors = getCategoricalColors( + DEFAULT_COLOR_STEPS, + id === activePalette?.name ? activePalette?.params : undefined + ); + return { + value: id, + title, + type: FIXED_PROGRESSION, + palette: activePalette?.params?.reverse ? colors.reverse() : colors, + 'data-test-subj': `${id}-palette`, + }; + }); + if (showCustomPalette) { + palettesToShow.push(getCustomPaletteConfig(palettes, activePalette)); + } + return ( + { + setPalette({ + type: 'palette', + name: newPalette, + }); + }} + valueOfSelected={activePalette?.name || defaultPaletteParams.name} + selectionDisplay="palette" + {...rest} + /> + ); +} diff --git a/x-pack/plugins/lens/public/shared_components/coloring/types.ts b/x-pack/plugins/lens/public/shared_components/coloring/types.ts new file mode 100644 index 00000000000000..d9a8edf0ccb62b --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export interface ColorStop { + color: string; + stop: number; +} + +export interface CustomPaletteParams { + name?: string; + reverse?: boolean; + rangeType?: 'number' | 'percent'; + continuity?: 'above' | 'below' | 'all' | 'none'; + progression?: 'fixed'; + rangeMin?: number; + rangeMax?: number; + stops?: ColorStop[]; + colorStops?: ColorStop[]; + steps?: number; +} + +export type RequiredPaletteParamTypes = Required; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts new file mode 100644 index 00000000000000..8aaab0923584d8 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts @@ -0,0 +1,399 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { + applyPaletteParams, + getContrastColor, + getDataMinMax, + getPaletteStops, + getStepValue, + isValidColor, + mergePaletteParams, + remapStopsByNewInterval, + reversePalette, + roundStopValues, +} from './utils'; + +describe('applyPaletteParams', () => { + const paletteRegistry = chartPluginMock.createPaletteRegistry(); + it('should return a palette stops array only by the name', () => { + expect( + applyPaletteParams( + paletteRegistry, + { name: 'default', type: 'palette', params: { name: 'default' } }, + { min: 0, max: 100 } + ) + ).toEqual([ + // stops are 0 and 50 by with a 20 offset (100 divided by 5 steps) for display + // the mock palette service has only 2 colors so tests are a bit off by that + { color: 'red', stop: 20 }, + { color: 'black', stop: 70 }, + ]); + }); + + it('should return a palette stops array reversed', () => { + expect( + applyPaletteParams( + paletteRegistry, + { name: 'default', type: 'palette', params: { name: 'default', reverse: true } }, + { min: 0, max: 100 } + ) + ).toEqual([ + { color: 'black', stop: 20 }, + { color: 'red', stop: 70 }, + ]); + }); +}); + +describe('remapStopsByNewInterval', () => { + it('should correctly remap the current palette from 0..1 to 0...100', () => { + expect( + remapStopsByNewInterval( + [ + { color: 'black', stop: 0 }, + { color: 'green', stop: 0.5 }, + { color: 'red', stop: 0.9 }, + ], + { newInterval: 100, oldInterval: 1, newMin: 0, oldMin: 0 } + ) + ).toEqual([ + { color: 'black', stop: 0 }, + { color: 'green', stop: 50 }, + { color: 'red', stop: 90 }, + ]); + + // now test the other way around + expect( + remapStopsByNewInterval( + [ + { color: 'black', stop: 0 }, + { color: 'green', stop: 50 }, + { color: 'red', stop: 90 }, + ], + { newInterval: 1, oldInterval: 100, newMin: 0, oldMin: 0 } + ) + ).toEqual([ + { color: 'black', stop: 0 }, + { color: 'green', stop: 0.5 }, + { color: 'red', stop: 0.9 }, + ]); + }); + + it('should correctly handle negative numbers to/from', () => { + expect( + remapStopsByNewInterval( + [ + { color: 'black', stop: -100 }, + { color: 'green', stop: -50 }, + { color: 'red', stop: -1 }, + ], + { newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -100 } + ) + ).toEqual([ + { color: 'black', stop: 0 }, + { color: 'green', stop: 50 }, + { color: 'red', stop: 99 }, + ]); + + // now map the other way around + expect( + remapStopsByNewInterval( + [ + { color: 'black', stop: 0 }, + { color: 'green', stop: 50 }, + { color: 'red', stop: 99 }, + ], + { newInterval: 100, oldInterval: 100, newMin: -100, oldMin: 0 } + ) + ).toEqual([ + { color: 'black', stop: -100 }, + { color: 'green', stop: -50 }, + { color: 'red', stop: -1 }, + ]); + + // and test also palettes that also contains negative values + expect( + remapStopsByNewInterval( + [ + { color: 'black', stop: -50 }, + { color: 'green', stop: 0 }, + { color: 'red', stop: 50 }, + ], + { newInterval: 100, oldInterval: 100, newMin: 0, oldMin: -50 } + ) + ).toEqual([ + { color: 'black', stop: 0 }, + { color: 'green', stop: 50 }, + { color: 'red', stop: 100 }, + ]); + }); +}); + +describe('getDataMinMax', () => { + it('should pick the correct min/max based on the current range type', () => { + expect(getDataMinMax('percent', { min: -100, max: 0 })).toEqual({ min: 0, max: 100 }); + }); + + it('should pick the correct min/max apply percent by default', () => { + expect(getDataMinMax(undefined, { min: -100, max: 0 })).toEqual({ min: 0, max: 100 }); + }); +}); + +describe('getPaletteStops', () => { + const paletteRegistry = chartPluginMock.createPaletteRegistry(); + it('should correctly compute a predefined palette stops definition from only the name', () => { + expect( + getPaletteStops(paletteRegistry, { name: 'mock' }, { dataBounds: { min: 0, max: 100 } }) + ).toEqual([ + { color: 'blue', stop: 20 }, + { color: 'yellow', stop: 70 }, + ]); + }); + + it('should correctly compute a predefined palette stops definition from explicit prevPalette (override)', () => { + expect( + getPaletteStops( + paletteRegistry, + { name: 'default' }, + { dataBounds: { min: 0, max: 100 }, prevPalette: 'mock' } + ) + ).toEqual([ + { color: 'blue', stop: 20 }, + { color: 'yellow', stop: 70 }, + ]); + }); + + it('should infer the domain from dataBounds but start from 0', () => { + expect( + getPaletteStops( + paletteRegistry, + { name: 'default', rangeType: 'number' }, + { dataBounds: { min: 1, max: 11 }, prevPalette: 'mock' } + ) + ).toEqual([ + { color: 'blue', stop: 2 }, + { color: 'yellow', stop: 7 }, + ]); + }); + + it('should override the minStop when requested', () => { + expect( + getPaletteStops( + paletteRegistry, + { name: 'default', rangeType: 'number' }, + { dataBounds: { min: 1, max: 11 }, mapFromMinValue: true } + ) + ).toEqual([ + { color: 'red', stop: 1 }, + { color: 'black', stop: 6 }, + ]); + }); + + it('should compute a display stop palette from custom colorStops defined by the user', () => { + expect( + getPaletteStops( + paletteRegistry, + { + name: 'custom', + rangeType: 'number', + colorStops: [ + { color: 'green', stop: 0 }, + { color: 'blue', stop: 40 }, + { color: 'red', stop: 80 }, + ], + }, + { dataBounds: { min: 0, max: 100 } } + ) + ).toEqual([ + { color: 'green', stop: 40 }, + { color: 'blue', stop: 80 }, + { color: 'red', stop: 100 }, + ]); + }); + + it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end', () => { + expect( + getPaletteStops( + paletteRegistry, + { + name: 'custom', + rangeType: 'number', + colorStops: [ + { color: 'green', stop: 0 }, + { color: 'blue', stop: 40 }, + { color: 'red', stop: 100 }, + ], + }, + { dataBounds: { min: 0, max: 100 } } + ) + ).toEqual([ + { color: 'green', stop: 40 }, + { color: 'blue', stop: 100 }, + { color: 'red', stop: 101 }, + ]); + }); + + it('should compute a display stop palette from custom colorStops defined by the user - handle stop at the end (fractional)', () => { + expect( + getPaletteStops( + paletteRegistry, + { + name: 'custom', + rangeType: 'number', + colorStops: [ + { color: 'green', stop: 0 }, + { color: 'blue', stop: 0.4 }, + { color: 'red', stop: 1 }, + ], + }, + { dataBounds: { min: 0, max: 1 } } + ) + ).toEqual([ + { color: 'green', stop: 0.4 }, + { color: 'blue', stop: 1 }, + { color: 'red', stop: 2 }, + ]); + }); + + it('should compute a display stop palette from custom colorStops defined by the user - stretch the stops to 100% percent', () => { + expect( + getPaletteStops( + paletteRegistry, + { + name: 'custom', + colorStops: [ + { color: 'green', stop: 0 }, + { color: 'blue', stop: 0.4 }, + { color: 'red', stop: 1 }, + ], + }, + { dataBounds: { min: 0, max: 1 } } + ) + ).toEqual([ + { color: 'green', stop: 0.4 }, + { color: 'blue', stop: 1 }, + { color: 'red', stop: 100 }, // default rangeType is percent, hence stretch to 100% + ]); + }); +}); + +describe('reversePalette', () => { + it('should correctly reverse color and stops', () => { + expect( + reversePalette([ + { color: 'red', stop: 0 }, + { color: 'green', stop: 0.5 }, + { color: 'blue', stop: 0.9 }, + ]) + ).toEqual([ + { color: 'blue', stop: 0 }, + { color: 'green', stop: 0.5 }, + { color: 'red', stop: 0.9 }, + ]); + }); +}); + +describe('mergePaletteParams', () => { + it('should return a full palette', () => { + expect(mergePaletteParams({ type: 'palette', name: 'myPalette' }, { reverse: true })).toEqual({ + type: 'palette', + name: 'myPalette', + params: { reverse: true }, + }); + }); +}); + +describe('isValidColor', () => { + it('should return ok for valid hex color notation', () => { + expect(isValidColor('#fff')).toBe(true); + expect(isValidColor('#ffffff')).toBe(true); + expect(isValidColor('#ffffffaa')).toBe(true); + }); + + it('should return false for non valid strings', () => { + expect(isValidColor('')).toBe(false); + expect(isValidColor('#')).toBe(false); + expect(isValidColor('#ff')).toBe(false); + expect(isValidColor('123')).toBe(false); + expect(isValidColor('rgb(1, 1, 1)')).toBe(false); + expect(isValidColor('rgba(1, 1, 1, 0)')).toBe(false); + expect(isValidColor('#ffffffgg')).toBe(false); + expect(isValidColor('#fff00')).toBe(false); + // this version of chroma does not support hex4 format + expect(isValidColor('#fffa')).toBe(false); + }); +}); + +describe('roundStopValues', () => { + it('should round very long values', () => { + expect(roundStopValues([{ color: 'red', stop: 0.1515 }])).toEqual([ + { color: 'red', stop: 0.15 }, + ]); + }); +}); + +describe('getStepValue', () => { + it('should compute the next step based on the last 2 stops', () => { + expect( + getStepValue( + // first arg is taken as max reference + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 50 }, + ], + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 50 }, + ], + 100 + ) + ).toBe(50); + + expect( + getStepValue( + // first arg is taken as max reference + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 80 }, + ], + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 50 }, + ], + 90 + ) + ).toBe(10); // 90 - 80 + + expect( + getStepValue( + // first arg is taken as max reference + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 100 }, + ], + [ + { color: 'red', stop: 0 }, + { color: 'red', stop: 50 }, + ], + 100 + ) + ).toBe(1); + }); +}); + +describe('getContrastColor', () => { + it('should pick the light color when the passed one is dark', () => { + expect(getContrastColor('#000', true)).toBe('#ffffff'); + expect(getContrastColor('#000', false)).toBe('#ffffff'); + }); + + it('should pick the dark color when the passed one is light', () => { + expect(getContrastColor('#fff', true)).toBe('#000000'); + expect(getContrastColor('#fff', false)).toBe('#000000'); + }); +}); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts new file mode 100644 index 00000000000000..89fceec533493f --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -0,0 +1,308 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import chroma from 'chroma-js'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; +import { isColorDark } from '@elastic/eui'; +import { + CUSTOM_PALETTE, + defaultPaletteParams, + DEFAULT_COLOR_STEPS, + DEFAULT_MAX_STOP, + DEFAULT_MIN_STOP, +} from './constants'; +import { CustomPaletteParams, ColorStop } from './types'; + +/** + * Some name conventions here: + * * `displayStops` => It's an additional transformation of `stops` into a [0, N] domain for the EUIPaletteDisplay component. + * * `stops` => final steps used to table coloring. It is a rightShift of the colorStops + * * `colorStops` => user's color stop inputs. Used to compute range min. + * + * When the user inputs the colorStops, they are designed to be the initial part of the color segment, + * so the next stops indicate where the previous stop ends. + * Both table coloring logic and EuiPaletteDisplay format implementation works differently than our current `colorStops`, + * by having the stop values at the end of each color segment rather than at the beginning: `stops` values are computed by a rightShift of `colorStops`. + * EuiPaletteDisplay has an additional requirement as it is always mapped against a domain [0, N]: from `stops` the `displayStops` are computed with + * some continuity enrichment and a remap against a [0, 100] domain to make the palette component work ok. + * + * These naming conventions would be useful to track the code flow in this feature as multiple transformations are happening + * for a single change. + */ + +export function applyPaletteParams( + palettes: PaletteRegistry, + activePalette: PaletteOutput, + dataBounds: { min: number; max: number } +) { + // make a copy of it as they have to be manipulated later on + let displayStops = getPaletteStops(palettes, activePalette?.params || {}, { + dataBounds, + }); + + if (activePalette?.params?.reverse && activePalette?.params?.name !== CUSTOM_PALETTE) { + displayStops = reversePalette(displayStops); + } + return displayStops; +} + +// Need to shift the Custom palette in order to correctly visualize it when in display mode +function shiftPalette(stops: ColorStop[], max: number) { + // shift everything right and add an additional stop at the end + const result = stops.map((entry, i, array) => ({ + ...entry, + stop: i + 1 < array.length ? array[i + 1].stop : max, + })); + if (stops[stops.length - 1].stop === max) { + // extends the range by a fair amount to make it work the extra case for the last stop === max + const computedStep = getStepValue(stops, result, max) || 1; + // do not go beyond the unit step in this case + const step = Math.min(1, computedStep); + result[stops.length - 1].stop = max + step; + } + return result; +} + +// Utility to remap color stops within new domain +export function remapStopsByNewInterval( + controlStops: ColorStop[], + { + newInterval, + oldInterval, + newMin, + oldMin, + }: { newInterval: number; oldInterval: number; newMin: number; oldMin: number } +) { + return (controlStops || []).map(({ color, stop }) => { + return { + color, + stop: newMin + ((stop - oldMin) * newInterval) / oldInterval, + }; + }); +} + +function getOverallMinMax( + params: CustomPaletteParams | undefined, + dataBounds: { min: number; max: number } +) { + const { min: dataMin, max: dataMax } = getDataMinMax(params?.rangeType, dataBounds); + const minStopValue = params?.colorStops?.[0]?.stop ?? Infinity; + const maxStopValue = params?.colorStops?.[params.colorStops.length - 1]?.stop ?? -Infinity; + const overallMin = Math.min(dataMin, minStopValue); + const overallMax = Math.max(dataMax, maxStopValue); + return { min: overallMin, max: overallMax }; +} + +export function getDataMinMax( + rangeType: CustomPaletteParams['rangeType'] | undefined, + dataBounds: { min: number; max: number } +) { + const dataMin = rangeType === 'number' ? dataBounds.min : DEFAULT_MIN_STOP; + const dataMax = rangeType === 'number' ? dataBounds.max : DEFAULT_MAX_STOP; + return { min: dataMin, max: dataMax }; +} + +/** + * This is a generic function to compute stops from the current parameters. + */ +export function getPaletteStops( + palettes: PaletteRegistry, + activePaletteParams: CustomPaletteParams, + // used to customize color resolution + { + prevPalette, + dataBounds, + mapFromMinValue, + }: { prevPalette?: string; dataBounds: { min: number; max: number }; mapFromMinValue?: boolean } +) { + const { min: minValue, max: maxValue } = getOverallMinMax(activePaletteParams, dataBounds); + const interval = maxValue - minValue; + const { stops: currentStops, ...otherParams } = activePaletteParams || {}; + + if (activePaletteParams.name === 'custom' && activePaletteParams?.colorStops) { + // need to generate the palette from the existing controlStops + return shiftPalette(activePaletteParams.colorStops, maxValue); + } + // generate a palette from predefined ones and customize the domain + const colorStopsFromPredefined = palettes + .get(prevPalette || activePaletteParams?.name || defaultPaletteParams.name) + .getCategoricalColors(defaultPaletteParams.steps, otherParams); + + const newStopsMin = mapFromMinValue ? minValue : interval / defaultPaletteParams.steps; + + const stops = remapStopsByNewInterval( + colorStopsFromPredefined.map((color, index) => ({ color, stop: index })), + { + newInterval: interval, + oldInterval: colorStopsFromPredefined.length, + newMin: newStopsMin, + oldMin: 0, + } + ); + return stops; +} + +export function reversePalette(paletteColorRepresentation: ColorStop[] = []) { + const stops = paletteColorRepresentation.map(({ stop }) => stop); + return paletteColorRepresentation + .map(({ color }, i) => ({ + color, + stop: stops[paletteColorRepresentation.length - i - 1], + })) + .reverse(); +} + +export function mergePaletteParams( + activePalette: PaletteOutput, + newParams: CustomPaletteParams +): PaletteOutput { + return { + ...activePalette, + params: { + ...activePalette.params, + ...newParams, + }, + }; +} + +function isValidPonyfill(colorString: string) { + // we're using an old version of chroma without the valid function + try { + chroma(colorString); + return true; + } catch (e) { + return false; + } +} + +export function isValidColor(colorString: string) { + // chroma can handle also hex values with alpha channel/transparency + // chroma accepts also hex without #, so test for it + return colorString !== '' && /^#/.test(colorString) && isValidPonyfill(colorString); +} + +export function roundStopValues(colorStops: ColorStop[]) { + return colorStops.map(({ color, stop }) => { + const roundedStop = Number(stop.toFixed(2)); + return { color, stop: roundedStop }; + }); +} + +// very simple heuristic: pick last two stops and compute a new stop based on the same distance +// if the new stop is above max, then reduce the step to reach max, or if zero then just 1. +// +// it accepts two series of stops as the function is used also when computing stops from colorStops +export function getStepValue(colorStops: ColorStop[], newColorStops: ColorStop[], max: number) { + const length = newColorStops.length; + // workout the steps from the last 2 items + const dataStep = newColorStops[length - 1].stop - newColorStops[length - 2].stop || 1; + let step = Number(dataStep.toFixed(2)); + if (max < colorStops[length - 1].stop + step) { + const diffToMax = max - colorStops[length - 1].stop; + // if the computed step goes way out of bound, fallback to 1, otherwise reach max + step = diffToMax > 0 ? diffToMax : 1; + } + return step; +} + +export function getSwitchToCustomParams( + palettes: PaletteRegistry, + activePalette: PaletteOutput, + newParams: CustomPaletteParams, + dataBounds: { min: number; max: number } +) { + // if it's already a custom palette just return the params + if (activePalette?.params?.name === CUSTOM_PALETTE) { + const stops = getPaletteStops( + palettes, + { + steps: DEFAULT_COLOR_STEPS, + ...activePalette.params, + ...newParams, + }, + { + dataBounds, + } + ); + return mergePaletteParams(activePalette, { + ...newParams, + stops, + }); + } + // prepare everything to switch to custom palette + const newPaletteParams = { + steps: DEFAULT_COLOR_STEPS, + ...activePalette.params, + ...newParams, + name: CUSTOM_PALETTE, + }; + + const stops = getPaletteStops(palettes, newPaletteParams, { + prevPalette: newPaletteParams.colorStops ? undefined : activePalette.name, + dataBounds, + }); + return mergePaletteParams( + { name: CUSTOM_PALETTE, type: 'palette' }, + { + ...newPaletteParams, + stops, + } + ); +} + +export function getColorStops( + palettes: PaletteRegistry, + colorStops: Required['stops'], + activePalette: PaletteOutput, + dataBounds: { min: number; max: number } +) { + // just forward the current stops if custom + if (activePalette?.name === CUSTOM_PALETTE) { + return colorStops; + } + // for predefined palettes create some stops, then drop the last one. + // we're using these as starting point for the user + let freshColorStops = getPaletteStops( + palettes, + { ...activePalette?.params }, + // mapFromMinValue is a special flag to offset the stops values + // used here to avoid a new remap/left shift + { dataBounds, mapFromMinValue: true } + ); + if (activePalette?.params?.reverse) { + freshColorStops = reversePalette(freshColorStops); + } + return freshColorStops; +} + +export function getContrastColor(color: string, isDarkTheme: boolean) { + const darkColor = isDarkTheme ? euiDarkVars.euiColorInk : euiLightVars.euiColorInk; + const lightColor = isDarkTheme ? euiDarkVars.euiColorGhost : euiLightVars.euiColorGhost; + return isColorDark(...chroma(color).rgb()) ? lightColor : darkColor; +} + +/** + * Same as stops, but remapped against a range 0-100 + */ +export function getStopsForFixedMode(stops: ColorStop[], colorStops?: ColorStop[]) { + const referenceStops = + colorStops || stops.map(({ color }, index) => ({ color, stop: 20 * index })); + const fallbackStops = stops; + + // what happens when user set two stops with the same value? we'll fallback to the display interval + const oldInterval = + referenceStops[referenceStops.length - 1].stop - referenceStops[0].stop || + fallbackStops[fallbackStops.length - 1].stop - fallbackStops[0].stop; + + return remapStopsByNewInterval(stops, { + newInterval: 100, + oldInterval, + newMin: 0, + oldMin: referenceStops[0].stop, + }); +} diff --git a/x-pack/plugins/lens/public/shared_components/helpers.ts b/x-pack/plugins/lens/public/shared_components/helpers.ts new file mode 100644 index 00000000000000..a9f35757c4cbfe --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/helpers.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useRef } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; + +export const useDebounceWithOptions = ( + fn: Function, + { skipFirstRender }: { skipFirstRender: boolean } = { skipFirstRender: false }, + ms?: number | undefined, + deps?: React.DependencyList | undefined +) => { + const isFirstRender = useRef(true); + const newDeps = [...(deps || []), isFirstRender]; + + return useDebounce( + () => { + if (skipFirstRender && isFirstRender.current) { + isFirstRender.current = false; + return; + } + return fn(); + }, + ms, + newDeps + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index ae57da976a8817..cf8536884acdf8 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -9,4 +9,7 @@ export * from './empty_placeholder'; export { ToolbarPopoverProps, ToolbarPopover } from './toolbar_popover'; export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; +export { TooltipWrapper } from './tooltip_wrapper'; +export * from './coloring'; export { useDebouncedValue } from './debounced_value'; +export * from './helpers'; diff --git a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx index b15a6749d4c2d1..6424dc8143f957 100644 --- a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx @@ -7,10 +7,9 @@ import React from 'react'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; -import { EuiColorPalettePicker } from '@elastic/eui'; +import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { NativeRenderer } from '../native_renderer'; export function PalettePicker({ palettes, @@ -21,6 +20,20 @@ export function PalettePicker({ activePalette?: PaletteOutput; setPalette: (palette: PaletteOutput) => void; }) { + const palettesToShow: EuiColorPalettePickerPaletteProps[] = palettes + .getAll() + .filter(({ internal }) => !internal) + .map(({ id, title, getCategoricalColors }) => { + return { + value: id, + title, + type: 'fixed', + palette: getCategoricalColors( + 10, + id === activePalette?.name ? activePalette?.params : undefined + ), + }; + }); return ( !internal) - .map(({ id, title, getColors }) => { - return { - value: id, - title, - type: 'fixed', - palette: getColors( - 10, - id === activePalette?.name ? activePalette?.params : undefined - ), - }; - })} + palettes={palettesToShow} onChange={(newPalette) => { setPalette({ type: 'palette', @@ -56,21 +56,6 @@ export function PalettePicker({ valueOfSelected={activePalette?.name || 'default'} selectionDisplay={'palette'} /> - {activePalette && palettes.get(activePalette.name).renderEditor && ( - { - setPalette({ - type: 'palette', - name: activePalette.name, - params: updater(activePalette.params), - }); - }, - }} - /> - )} ); diff --git a/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx b/x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx similarity index 100% rename from x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx rename to x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 5a632e03f8f36c..984fbf5555949b 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -9,6 +9,7 @@ import { IconType } from '@elastic/eui/src/components/icon/icon'; import { CoreSetup } from 'kibana/public'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { SavedObjectReference } from 'kibana/public'; +import { MutableRefObject } from 'react'; import { RowClickContext } from '../../../../src/plugins/ui_actions/public'; import { ExpressionAstExpression, @@ -391,13 +392,14 @@ export type VisualizationDimensionEditorProps = VisualizationConfig groupId: string; accessor: string; setState: (newState: T) => void; + panelRef: MutableRefObject; }; export interface AccessorConfig { columnId: string; triggerIcon?: 'color' | 'disabled' | 'colorBy' | 'none' | 'invisible'; color?: string; - palette?: string[]; + palette?: string[] | Array<{ color: string; stop: number }>; } export type VisualizationDimensionGroupConfig = SharedDimensionProps & { diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index d2e87ece5b5ec8..ef0c350f209619 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -118,7 +118,7 @@ export function getAccessorColorConfig( ); const customColor = currentYConfig?.color || - paletteService.get(currentPalette.name).getColor( + paletteService.get(currentPalette.name).getCategoricalColor( [ { name: columnToLabel[accessor] || accessor, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index e3b4565913ad87..608971d281981f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -798,7 +798,7 @@ export function XYChart({ ), }, ]; - return paletteService.get(palette.name).getColor( + return paletteService.get(palette.name).getCategoricalColor( seriesLayers, { maxDepth: 1, diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx index b07feb85892e53..843680e3f28ac6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx @@ -7,14 +7,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ToolbarPopover } from '../../shared_components'; +import { ToolbarPopover, TooltipWrapper } from '../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; import { LineCurveOption } from './line_curve_option'; import { FillOpacityOption } from './fill_opacity_option'; import { XYState } from '../types'; import { hasHistogramSeries } from '../state_helpers'; import { ValidLayer } from '../types'; -import { TooltipWrapper } from '../tooltip_wrapper'; import { FramePublicAPI } from '../../types'; function getValueLabelDisableReason({ 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 aa4b91b840db37..8fbc8e8b2ef7ab 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -481,12 +481,12 @@ describe('xy_visualization', () => { it('should query palette to fill in colors for other dimensions', () => { const palette = paletteServiceMock.get('default'); - (palette.getColor as jest.Mock).mockClear(); + (palette.getCategoricalColor as jest.Mock).mockClear(); const accessorConfig = callConfigAndFindYConfig({}, 'c'); expect(accessorConfig.triggerIcon).toEqual('color'); // black is the color returned from the palette mock expect(accessorConfig.color).toEqual('black'); - expect(palette.getColor).toHaveBeenCalledWith( + expect(palette.getCategoricalColor).toHaveBeenCalledWith( [ { name: 'c', @@ -505,9 +505,9 @@ describe('xy_visualization', () => { label: 'Overwritten label', }); const palette = paletteServiceMock.get('default'); - (palette.getColor as jest.Mock).mockClear(); + (palette.getCategoricalColor as jest.Mock).mockClear(); callConfigAndFindYConfig({}, 'c'); - expect(palette.getColor).toHaveBeenCalledWith( + expect(palette.getCategoricalColor).toHaveBeenCalledWith( [ expect.objectContaining({ name: 'Overwritten label', @@ -526,7 +526,7 @@ describe('xy_visualization', () => { }, 'c' ); - expect(palette.getColor).toHaveBeenCalled(); + expect(palette.getCategoricalColor).toHaveBeenCalled(); }); it('should not show any indicator as long as there is no data', () => { @@ -551,7 +551,7 @@ describe('xy_visualization', () => { it('should show current palette for break down by dimension', () => { const palette = paletteServiceMock.get('mock'); const customColors = ['yellow', 'green']; - (palette.getColors as jest.Mock).mockReturnValue(customColors); + (palette.getCategoricalColors as jest.Mock).mockReturnValue(customColors); const breakdownConfig = callConfigForBreakdownConfigs({ palette: { type: 'palette', name: 'mock', params: {} }, splitAccessor: 'd', @@ -570,9 +570,9 @@ describe('xy_visualization', () => { paletteGetter.mockReturnValue({ id: 'default', title: '', - getColors: jest.fn(), + getCategoricalColors: jest.fn(), toExpression: jest.fn(), - getColor: jest.fn().mockReturnValueOnce('blue').mockReturnValueOnce('green'), + getCategoricalColor: jest.fn().mockReturnValueOnce('blue').mockReturnValueOnce('green'), }); const yConfigs = callConfigForYConfigs({}); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 19cfcb1a60cc7c..fa9d46be11d686 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -235,7 +235,7 @@ export const getXyVisualization = ({ triggerIcon: 'colorBy', palette: paletteService .get(layer.palette?.name || 'default') - .getColors(10, layer.palette?.params), + .getCategoricalColors(10, layer.palette?.params), }, ] : [], 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 0bafbead7d5438..bc10236cf1977e 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 @@ -260,6 +260,7 @@ describe('XY Config panels', () => { state={{ ...state, layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }] }} formatFactory={jest.fn()} paletteService={chartPluginMock.createPaletteRegistry()} + panelRef={React.createRef()} /> ); @@ -283,6 +284,7 @@ describe('XY Config panels', () => { state={state} formatFactory={jest.fn()} paletteService={chartPluginMock.createPaletteRegistry()} + panelRef={React.createRef()} /> ); @@ -326,6 +328,7 @@ describe('XY Config panels', () => { }} formatFactory={jest.fn()} paletteService={chartPluginMock.createPaletteRegistry()} + panelRef={React.createRef()} /> ); @@ -365,6 +368,7 @@ describe('XY Config panels', () => { }} formatFactory={jest.fn()} paletteService={chartPluginMock.createPaletteRegistry()} + panelRef={React.createRef()} /> ); 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 a6517894654ede..48f0cacf75938a 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 @@ -41,9 +41,8 @@ import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_h import { trackUiEvent } from '../lens_ui_telemetry'; import { LegendSettingsPopover } from '../shared_components'; import { AxisSettingsPopover } from './axis_settings_popover'; -import { TooltipWrapper } from './tooltip_wrapper'; import { getAxesConfiguration, GroupsConfiguration } from './axes_configuration'; -import { PalettePicker } from '../shared_components'; +import { PalettePicker, TooltipWrapper } from '../shared_components'; import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; import { getScaleType, getSortedAccessors } from './to_expression'; import { VisualOptionsPopover } from './visual_options_popover/visual_options_popover'; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 1652e78d3d2cbf..4fce4c276c3360 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -112,7 +112,7 @@ export async function getChartsPaletteServiceGetColor(): Promise< const chartConfiguration = { syncColors: true }; return (value: string) => { const series = [{ name: value, rankAtDepth: 0, totalSeriesAtDepth: 1 }]; - const color = paletteDefinition.getColor(series, chartConfiguration); + const color = paletteDefinition.getCategoricalColor(series, chartConfiguration); return color ? color : '#3d3d3d'; }; } diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts index c7c3f3ae9b2804..2b05f231e509ff 100644 --- a/x-pack/plugins/ml/common/types/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -112,6 +112,10 @@ export interface ExplorerAppState { viewByFieldName?: string; viewByPerPage?: number; viewByFromPage?: number; + /** + * Indicated severity threshold for both swim lanes + */ + severity?: number; }; mlExplorerFilter: { influencersFilterQuery?: InfluencersFilterQuery; diff --git a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx index 12fbaece54fac1..719b5c4aa4ad58 100644 --- a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx @@ -13,7 +13,7 @@ import { JobSelectorControl } from './job_selector'; import { useMlKibana } from '../application/contexts/kibana'; import { jobsApiProvider } from '../application/services/ml_api_service/jobs'; import { HttpService } from '../application/services/http_service'; -import { SeverityControl } from './severity_control'; +import { SeverityControl } from '../application/components/severity_control'; import { ResultTypeSelector } from './result_type_selector'; import { alertingApiProvider } from '../application/services/ml_api_service/alerting'; import { PreviewAlertCondition } from './preview_alert_condition'; diff --git a/x-pack/plugins/ml/public/alerting/severity_control/severity_control.tsx b/x-pack/plugins/ml/public/alerting/severity_control/severity_control.tsx deleted file mode 100644 index b1cd808643ca2f..00000000000000 --- a/x-pack/plugins/ml/public/alerting/severity_control/severity_control.tsx +++ /dev/null @@ -1,84 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { FC } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFormRow, EuiRange, EuiRangeProps } from '@elastic/eui'; -import { SEVERITY_OPTIONS } from '../../application/components/controls/select_severity/select_severity'; -import { ANOMALY_THRESHOLD } from '../../../common'; -import './styles.scss'; - -export interface SeveritySelectorProps { - value: number | undefined; - onChange: (value: number) => void; -} - -const MAX_ANOMALY_SCORE = 100; - -export const SeverityControl: FC = React.memo(({ value, onChange }) => { - const levels: EuiRangeProps['levels'] = [ - { - min: ANOMALY_THRESHOLD.LOW, - max: ANOMALY_THRESHOLD.MINOR - 1, - color: 'success', - }, - { - min: ANOMALY_THRESHOLD.MINOR, - max: ANOMALY_THRESHOLD.MAJOR - 1, - color: 'primary', - }, - { - min: ANOMALY_THRESHOLD.MAJOR, - max: ANOMALY_THRESHOLD.CRITICAL, - color: 'warning', - }, - { - min: ANOMALY_THRESHOLD.CRITICAL, - max: MAX_ANOMALY_SCORE, - color: 'danger', - }, - ]; - - const toggleButtons = SEVERITY_OPTIONS.map((v) => ({ - value: v.val, - label: v.display, - })); - - return ( - - } - > - { - // @ts-ignore Property 'value' does not exist on type 'EventTarget' | (EventTarget & HTMLInputElement) - onChange(Number(e.target.value)); - }} - showLabels - showValue - aria-label={i18n.translate('xpack.ml.severitySelector.formControlLabel', { - defaultMessage: 'Select severity threshold', - })} - showTicks - ticks={toggleButtons} - levels={levels} - data-test-subj={'mlAnomalyAlertScoreSelection'} - /> - - ); -}); diff --git a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts index f8e9a3b44e7e83..9fb41d15dd94a5 100644 --- a/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/public/application/capabilities/check_capabilities.ts @@ -25,7 +25,7 @@ export function checkGetManagementMlJobsResolver() { if (isManageML === true && isPlatinumOrTrialLicense === true) { return resolve({ mlFeatureEnabledInSpace }); } else { - return reject(); + return reject({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }); } }) .catch((e) => { diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index 7339b50d4ab340..262daae9d6469c 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -232,6 +232,7 @@ class LinksMenuUI extends Component { } const categorizationFieldName = job.analysis_config.categorization_field_name; const datafeedIndices = job.datafeed_config.indices; + // Find the type of the categorization field i.e. text (preferred) or keyword. // Uses the first matching field found in the list of indices in the datafeed_config. // attempt to load the field type using each index. we have to do it this way as _field_caps @@ -349,7 +350,7 @@ class LinksMenuUI extends Component { getFieldTypeFromMapping(index, categorizationFieldName) .then((resp) => { if (resp !== '') { - createAndOpenUrl(index, resp); + createAndOpenUrl(datafeedIndices.join(), resp); } else { i++; if (i < datafeedIndices.length) { diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx index 348c400b6d5a9c..f1ef62ddc90d49 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx @@ -6,7 +6,7 @@ */ import React, { FC } from 'react'; -import { EuiSelect } from '@elastic/eui'; +import { EuiIcon, EuiSelect, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { usePageUrlState } from '../../../util/url_state'; @@ -78,8 +78,22 @@ export const SelectIntervalUI: FC = ({ interval, onChange return ( + + + } + compressed + id="selectInterval" options={OPTIONS} - className="ml-select-interval" value={interval.val} onChange={handleOnChange} /> diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx index e8766ea16c0021..3fe50a8b46d550 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx @@ -8,11 +8,11 @@ /* * React component for rendering a select element with threshold levels. */ -import React, { Fragment, FC } from 'react'; +import React, { Fragment, FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText, EuiSuperSelectProps } from '@elastic/eui'; import { getSeverityColor } from '../../../../../common/util/anomaly_utils'; import { usePageUrlState } from '../../../util/url_state'; @@ -124,23 +124,34 @@ export const SelectSeverity: FC = ({ classNames } = { classNames: '' }) = return ; }; -export const SelectSeverityUI: FC<{ - classNames?: string; - severity: TableSeverity; - onChange: (s: TableSeverity) => void; -}> = ({ classNames = '', severity, onChange }) => { +export const SelectSeverityUI: FC< + Omit, 'onChange' | 'options'> & { + classNames?: string; + severity: TableSeverity; + onChange: (s: TableSeverity) => void; + } +> = ({ classNames = '', severity, onChange, compressed }) => { const handleOnChange = (valueDisplay: string) => { onChange(optionValueToThreshold(optionsMap[valueDisplay])); }; + const options = useMemo(() => { + return getSeverityOptions(); + }, []); + return ( ); }; diff --git a/x-pack/plugins/ml/public/alerting/severity_control/index.ts b/x-pack/plugins/ml/public/application/components/severity_control/index.ts similarity index 100% rename from x-pack/plugins/ml/public/alerting/severity_control/index.ts rename to x-pack/plugins/ml/public/application/components/severity_control/index.ts diff --git a/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx new file mode 100644 index 00000000000000..7be72b84302334 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiRange, + EuiRangeProps, +} from '@elastic/eui'; +import { ANOMALY_THRESHOLD } from '../../../../common'; +import './styles.scss'; + +export interface SeveritySelectorProps { + value: number | undefined; + onChange: (value: number) => void; +} + +const MAX_ANOMALY_SCORE = 100; + +export const SeverityControl: FC = React.memo(({ value, onChange }) => { + const levels: EuiRangeProps['levels'] = [ + { + min: ANOMALY_THRESHOLD.LOW, + max: ANOMALY_THRESHOLD.MINOR - 1, + color: 'success', + }, + { + min: ANOMALY_THRESHOLD.MINOR, + max: ANOMALY_THRESHOLD.MAJOR - 1, + color: 'primary', + }, + { + min: ANOMALY_THRESHOLD.MAJOR, + max: ANOMALY_THRESHOLD.CRITICAL, + color: 'warning', + }, + { + min: ANOMALY_THRESHOLD.CRITICAL, + max: MAX_ANOMALY_SCORE, + color: 'danger', + }, + ]; + + const label = i18n.translate('xpack.ml.severitySelector.formControlLabel', { + defaultMessage: 'Severity', + }); + + const resultValue = value ?? ANOMALY_THRESHOLD.LOW; + + const onChangeCallback = ( + e: React.ChangeEvent | React.MouseEvent + ) => { + // @ts-ignore Property 'value' does not exist on type 'EventTarget' | (EventTarget & HTMLInputElement) + onChange(Number(e.target.value)); + }; + + const ticks = new Array(5).fill(null).map((x, i) => { + const v = i * 25; + return { value: v, label: v }; + }); + + return ( + + + + + + + + + + + ); +}); diff --git a/x-pack/plugins/ml/public/alerting/severity_control/styles.scss b/x-pack/plugins/ml/public/application/components/severity_control/styles.scss similarity index 100% rename from x-pack/plugins/ml/public/alerting/severity_control/styles.scss rename to x-pack/plugins/ml/public/application/components/severity_control/styles.scss diff --git a/x-pack/plugins/ml/public/application/explorer/_explorer.scss b/x-pack/plugins/ml/public/application/explorer/_explorer.scss index c08020325428dd..d9d60ecfae67df 100644 --- a/x-pack/plugins/ml/public/application/explorer/_explorer.scss +++ b/x-pack/plugins/ml/public/application/explorer/_explorer.scss @@ -40,14 +40,6 @@ $borderRadius: $euiBorderRadius / 2; font-size: $euiFontSizeXS; } } - - .ml-anomalies-controls { - padding-top: $euiSizeXS; - - #show_charts_checkbox_control { - padding-top: $euiSizeL; - } - } } .mlSwimLaneContainer { diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index a5d50f1070f5ba..621ce442047309 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -83,6 +83,7 @@ export interface LoadExplorerDataConfig { viewByFromPage: number; viewByPerPage: number; swimlaneContainerWidth: number; + swimLaneSeverity: number; } export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfig => { @@ -135,6 +136,7 @@ const loadExplorerDataProvider = ( swimlaneContainerWidth, viewByFromPage, viewByPerPage, + swimLaneSeverity, } = config; const combinedJobRecords: Record = selectedJobs.reduce((acc, job) => { @@ -192,7 +194,13 @@ const loadExplorerDataProvider = ( influencersFilterQuery ) : Promise.resolve({}), - overallState: memoizedLoadOverallData(lastRefresh, selectedJobs, swimlaneContainerWidth), + overallState: memoizedLoadOverallData( + lastRefresh, + selectedJobs, + swimlaneContainerWidth, + undefined, + swimLaneSeverity + ), tableData: memoizedLoadAnomaliesTableData( lastRefresh, selectedCells, @@ -278,7 +286,9 @@ const loadExplorerDataProvider = ( viewByPerPage, viewByFromPage, swimlaneContainerWidth, - influencersFilterQuery + influencersFilterQuery, + undefined, + swimLaneSeverity ), }).pipe( map(({ viewBySwimlaneState, filteredTopInfluencers }) => { diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 1e8f54d10491d9..8375b0a0b1dfc5 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, useMemo, useState } from 'react'; +import React, { FC, useCallback, useMemo, useState } from 'react'; import { isEqual } from 'lodash'; import { EuiPanel, @@ -14,7 +14,6 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - EuiFormRow, EuiSelect, EuiTitle, EuiSpacer, @@ -35,7 +34,9 @@ import { ExplorerNoInfluencersFound } from './components/explorer_no_influencers import { SwimlaneContainer } from './swimlane_container'; import { AppStateSelectedCells, OverallSwimlaneData, ViewBySwimLaneData } from './explorer_utils'; import { NoOverallData } from './components/no_overall_data'; +import { SeverityControl } from '../components/severity_control'; import { AnomalyTimelineHelpPopover } from './anomaly_timeline_help_popover'; +import { isDefined } from '../../../common/types/guards'; function mapSwimlaneOptionsToEuiOptions(options: string[]) { return options.map((option) => ({ @@ -76,10 +77,8 @@ export const AnomalyTimeline: FC = React.memo( filterActive, filteredFields, maskAll, - overallSwimlaneData, selectedCells, viewByLoadedForTimeFormatted, - viewBySwimlaneData, viewBySwimlaneDataLoading, viewBySwimlaneFieldName, viewBySwimlaneOptions, @@ -89,6 +88,9 @@ export const AnomalyTimeline: FC = React.memo( swimlaneLimit, loading, overallAnnotations, + swimLaneSeverity, + overallSwimlaneData, + viewBySwimlaneData, } = explorerState; const annotations = useMemo(() => overallAnnotations.annotationsData, [overallAnnotations]); @@ -128,7 +130,7 @@ export const AnomalyTimeline: FC = React.memo( return ( <> - +

@@ -139,68 +141,10 @@ export const AnomalyTimeline: FC = React.memo(

- {viewBySwimlaneOptions.length > 0 && ( - <> - - - - - } - display={'columnCompressed'} - > - explorerService.setViewBySwimlaneFieldName(e.target.value)} - /> - - - {selectedCells ? ( - - - - - - ) : null} - -
- {viewByLoadedForTimeFormatted && ( - - )} - {viewByLoadedForTimeFormatted === undefined && ( - - )} - {filterActive === true && viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( - - )} -
-
- - )} + + + + {menuItems.length > 0 && ( @@ -226,10 +170,79 @@ export const AnomalyTimeline: FC = React.memo( )} +
+ + + + + {viewBySwimlaneOptions.length > 0 && ( + <> + + explorerService.setViewBySwimlaneFieldName(e.target.value)} + /> + + + )} + + + { + explorerService.setSwimLaneSeverity(update); + }, [])} + /> + + + + + - +
+ {viewByLoadedForTimeFormatted && ( + + )} + {isDefined(viewByLoadedForTimeFormatted) ? null : ( + + )} + {filterActive === true && viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( + + )} +
+ + {selectedCells ? ( + + + + + + ) : null}
@@ -249,6 +262,7 @@ export const AnomalyTimeline: FC = React.memo( noDataWarning={} showTimeline={false} annotationsData={annotations} + showLegend={false} /> @@ -266,7 +280,7 @@ export const AnomalyTimeline: FC = React.memo( }) } timeBuckets={timeBuckets} - showLegend={true} + showLegend={false} swimlaneData={viewBySwimlaneData as ViewBySwimLaneData} swimlaneType={SWIMLANE_TYPE.VIEW_BY} selection={selectedCells} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 7cc1d0d86e2ff2..4b241c47a267b0 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -19,9 +19,7 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiFormRow, EuiHorizontalRule, - EuiIcon, EuiIconTip, EuiPage, EuiPageBody, @@ -29,7 +27,6 @@ import { EuiPageHeaderSection, EuiSpacer, EuiTitle, - EuiToolTip, EuiLoadingContent, EuiPanel, EuiAccordion, @@ -78,6 +75,7 @@ import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/ import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { ML_APP_URL_GENERATOR } from '../../../common/constants/ml_url_generator'; import { AnomalyContextMenu } from './anomaly_context_menu'; +import { isDefined } from '../../../common/types/guards'; const ExplorerPage = ({ children, @@ -263,6 +261,7 @@ export class ExplorerUI extends React.Component { selectedCells, selectedJobs, tableData, + swimLaneSeverity, } = this.props.explorerState; const { annotationsData, aggregations, error: annotationsError } = annotations; @@ -276,6 +275,8 @@ export class ExplorerUI extends React.Component { (hasResults && overallSwimlaneData.points.some((v) => v.value > 0)) || tableData.anomalies?.length > 0; + const hasActiveFilter = isDefined(swimLaneSeverity); + if (noJobsFound && !loading) { return ( @@ -284,7 +285,7 @@ export class ExplorerUI extends React.Component { ); } - if (hasResultsWithAnomalies === false && !loading) { + if (!hasResultsWithAnomalies && !loading && !hasActiveFilter) { return ( + + {annotationsError !== undefined && ( <> )} - {loading === false && tableData.anomalies?.length && ( + {loading === false && tableData.anomalies?.length ? ( - )} + ) : null} {annotationsData.length > 0 && ( <> @@ -476,47 +479,16 @@ export class ExplorerUI extends React.Component {
- - - - - + + + - - - - {i18n.translate('xpack.ml.explorer.intervalLabel', { - defaultMessage: 'Interval', - })} - - - - } - > - - + + {chartsData.seriesToPlot.length > 0 && selectedCells !== undefined && ( - - - - + + )} @@ -524,7 +496,7 @@ export class ExplorerUI extends React.Component {
- {showCharts && ( + {showCharts ? ( - )} + ) : null}
= ( }) => { return ( <> - - - - - + + + diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts index 4398a4b2c2be75..d737c4733b9cba 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_constants.ts @@ -33,6 +33,7 @@ export const EXPLORER_ACTION = { SET_VIEW_BY_SWIMLANE_LOADING: 'setViewBySwimlaneLoading', SET_VIEW_BY_PER_PAGE: 'setViewByPerPage', SET_VIEW_BY_FROM_PAGE: 'setViewByFromPage', + SET_SWIM_LANE_SEVERITY: 'setSwimLaneSeverity', }; export const FILTER_ACTION = { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 343ba88655e4e0..7721532b34338f 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -79,6 +79,10 @@ const explorerAppState$: Observable = explorerState$.pipe( appState.mlExplorerSwimlane.viewByPerPage = state.viewByPerPage; } + if (state.swimLaneSeverity !== undefined) { + appState.mlExplorerSwimlane.severity = state.swimLaneSeverity; + } + if (state.filterActive) { appState.mlExplorerFilter.influencersFilterQuery = state.influencersFilterQuery; appState.mlExplorerFilter.filterActive = state.filterActive; @@ -161,6 +165,9 @@ export const explorerService = { setViewByPerPage: (payload: number) => { explorerAction$.next({ type: EXPLORER_ACTION.SET_VIEW_BY_PER_PAGE, payload }); }, + setSwimLaneSeverity: (payload: number) => { + explorerAction$.next({ type: EXPLORER_ACTION.SET_SWIM_LANE_SEVERITY, payload }); + }, }; export type ExplorerService = typeof explorerService; diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts index 15e0caa29af39f..74867af5f89879 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts @@ -149,6 +149,15 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo }; break; + case EXPLORER_ACTION.SET_SWIM_LANE_SEVERITY: + nextState = { + ...state, + // reset current page on the page size change + viewByFromPage: 1, + swimLaneSeverity: payload, + }; + break; + default: nextState = state; } @@ -181,7 +190,9 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo ...nextState, swimlaneBucketInterval, viewByLoadedForTimeFormatted: timeRange - ? formatHumanReadableDateTime(timeRange.earliestMs) + ? `${formatHumanReadableDateTime(timeRange.earliestMs)} - ${formatHumanReadableDateTime( + timeRange.latestMs + )}` : null, viewBySwimlaneFieldName, viewBySwimlaneOptions, diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index 2365e4e4689026..8a152ab1cadc31 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -58,6 +58,7 @@ export interface ExplorerState { viewByFromPage: number; viewBySwimlaneOptions: string[]; swimlaneLimit?: number; + swimLaneSeverity?: number; } function getDefaultIndexPattern() { diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 41bbe5b66a605f..d959328218a187 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -68,6 +68,10 @@ declare global { const RESIZE_THROTTLE_TIME_MS = 500; const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; +/** + * Minimum container height to make sure "No data" message is displayed without overflow. + */ +const MIN_CONTAINER_HEIGHT = 40; const Y_AXIS_HEIGHT = 24; @@ -245,7 +249,10 @@ export const SwimlaneContainer: FC = ({ return isLoading ? containerHeightRef.current : // TODO update when elastic charts X label will be fixed - rowsCount * CELL_HEIGHT + LEGEND_HEIGHT + (true ? Y_AXIS_HEIGHT : 0); + Math.max( + rowsCount * CELL_HEIGHT + (showLegend ? LEGEND_HEIGHT : 0) + (true ? Y_AXIS_HEIGHT : 0), + MIN_CONTAINER_HEIGHT + ); }, [isLoading, rowsCount, showTimeline]); useEffect(() => { @@ -331,7 +338,7 @@ export const SwimlaneContainer: FC = ({ brushArea: { stroke: isDarkTheme ? 'rgb(255, 255, 255)' : 'rgb(105, 112, 125)', }, - maxLegendHeight: LEGEND_HEIGHT, + ...(showLegend ? { maxLegendHeight: LEGEND_HEIGHT } : {}), timeZone: 'UTC', }; }, [ @@ -463,7 +470,7 @@ export const SwimlaneContainer: FC = ({ )} {!isLoading && !showSwimlane && ( {noDataWarning}} /> diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js index 75bc93c8dc65ea..9da97f40f5ec66 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js @@ -15,6 +15,7 @@ import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../../src/plugins import { getPartitioningFieldNames } from '../../../../../common/util/job_utils'; import { parseInterval } from '../../../../../common/util/parse_interval'; import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils'; +import { getIndexPatternIdFromName } from '../../../util/index_utils'; import { ml } from '../../../services/ml_api_service'; import { mlJobService } from '../../../services/job_service'; import { escapeForElasticsearchQuery } from '../../../util/string_utils'; @@ -38,7 +39,7 @@ export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { } // For the Discover option, set the default index pattern to that - // which matches the (first) index configured in the job datafeed. + // which matches the indices configured in the job datafeed. const datafeedConfig = job.datafeed_config; if ( indexPatterns !== undefined && @@ -47,16 +48,9 @@ export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { datafeedConfig.indices !== undefined && datafeedConfig.indices.length > 0 ) { - const datafeedIndex = datafeedConfig.indices[0]; - let defaultIndexPattern = indexPatterns.find((indexPattern) => { - return indexPattern.title === datafeedIndex; - }); - - if (defaultIndexPattern === undefined) { - defaultIndexPattern = indexPatterns[0]; - } - - kibanaSettings.discoverIndexPatternId = defaultIndexPattern.id; + const defaultIndexPatternId = + getIndexPatternIdFromName(datafeedConfig.indices.join()) ?? indexPatterns[0].id; + kibanaSettings.discoverIndexPatternId = defaultIndexPatternId; } return { diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx index 18d8a42b76cb07..0d785f1918b0b5 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/access_denied_page.tsx @@ -31,8 +31,8 @@ export const AccessDeniedPage = () => (

@@ -42,7 +42,7 @@ export const AccessDeniedPage = () => ( = ({ basePath }) => ( + + + + + + +

+ +

+
+
+
+ + + + +

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

+
+
+
+
+
+
+); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 2dc46bcf8fb41a..ca62ef9aaf0af1 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -38,6 +38,7 @@ import { getDocLinks } from '../../../../util/dependency_cache'; import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_view/index'; import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list'; import { AccessDeniedPage } from '../access_denied_page'; +import { InsufficientLicensePage } from '../insufficient_license_page'; import { SharePluginStart } from '../../../../../../../../../src/plugins/share/public'; import type { SpacesPluginStart } from '../../../../../../../spaces/public'; import { JobSpacesSyncFlyout } from '../../../../components/job_spaces_sync'; @@ -128,6 +129,7 @@ export const JobsListPage: FC<{ const spacesEnabled = spacesApi !== undefined; const [initialized, setInitialized] = useState(false); const [accessDenied, setAccessDenied] = useState(false); + const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true); const [showSyncFlyout, setShowSyncFlyout] = useState(false); const [isMlEnabledInSpace, setIsMlEnabledInSpace] = useState(false); const tabs = useTabs(isMlEnabledInSpace, spacesApi); @@ -139,7 +141,11 @@ export const JobsListPage: FC<{ const { mlFeatureEnabledInSpace } = await checkGetManagementMlJobsResolver(); setIsMlEnabledInSpace(mlFeatureEnabledInSpace); } catch (e) { - setAccessDenied(true); + if (e.mlFeatureEnabledInSpace && e.isPlatinumOrTrialLicense === false) { + setIsPlatinumOrTrialLicense(false); + } else { + setAccessDenied(true); + } } setInitialized(true); }; @@ -191,6 +197,10 @@ export const JobsListPage: FC<{ return ; } + if (isPlatinumOrTrialLicense === false) { + return ; + } + return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 3e5cf252230a26..a0a81f77b7b087 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -177,7 +177,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim explorerService.setFilterData(filterData); } - const { viewByFieldName, viewByFromPage, viewByPerPage } = + const { viewByFieldName, viewByFromPage, viewByPerPage, severity } = explorerUrlState?.mlExplorerSwimlane ?? {}; if (viewByFieldName !== undefined) { @@ -191,6 +191,10 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim if (viewByFromPage !== undefined) { explorerService.setViewByFromPage(viewByFromPage); } + + if (severity !== undefined) { + explorerService.setSwimLaneSeverity(severity); + } }, []); /** Sync URL state with {@link explorerService} state */ @@ -238,6 +242,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim swimlaneContainerWidth: explorerState.swimlaneContainerWidth, viewByPerPage: explorerState.viewByPerPage, viewByFromPage: explorerState.viewByFromPage, + swimLaneSeverity: explorerState.swimLaneSeverity, } : undefined; diff --git a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts index 54d9626edf26c0..e11eb4048c374b 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_timeline_service.ts @@ -98,7 +98,8 @@ export class AnomalyTimelineService { public async loadOverallData( selectedJobs: ExplorerJob[], chartWidth?: number, - bucketInterval?: TimeBucketsInterval + bucketInterval?: TimeBucketsInterval, + overallScore?: number ): Promise { const interval = bucketInterval ?? this.getSwimlaneBucketInterval(selectedJobs, chartWidth!); @@ -127,7 +128,8 @@ export class AnomalyTimelineService { 1, overallBucketsBounds.min.valueOf(), overallBucketsBounds.max.valueOf(), - interval.asSeconds() + 's' + interval.asSeconds() + 's', + overallScore ); const overallSwimlaneData = this.processOverallResults( resp.results, @@ -161,7 +163,8 @@ export class AnomalyTimelineService { fromPage: number, swimlaneContainerWidth?: number, influencersFilterQuery?: any, - bucketInterval?: TimeBucketsInterval + bucketInterval?: TimeBucketsInterval, + swimLaneSeverity?: number ): Promise { const timefilterBounds = this.getTimeBounds(); @@ -195,7 +198,8 @@ export class AnomalyTimelineService { searchBounds.max.valueOf(), intervalMs, perPage, - fromPage + fromPage, + swimLaneSeverity ); } else { response = await this.mlResultsService.getInfluencerValueMaxScoreByTime( @@ -208,7 +212,8 @@ export class AnomalyTimelineService { swimlaneLimit, perPage, fromPage, - influencersFilterQuery + influencersFilterQuery, + swimLaneSeverity ); } diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index bf6b752faa8da6..f8ec4b64883160 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -323,14 +323,22 @@ export function mlApiServicesProvider(httpService: HttpService) { bucketSpan, start, end, + overallScore, }: { jobId: string; topN: string; bucketSpan: string; start: number; end: number; + overallScore?: number; }) { - const body = JSON.stringify({ topN, bucketSpan, start, end }); + const body = JSON.stringify({ + topN, + bucketSpan, + start, + end, + ...(overallScore ? { overall_score: overallScore } : {}), + }); return httpService.http({ path: `${basePath()}/anomaly_detectors/${jobId}/results/overall_buckets`, method: 'POST', diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index 6161eeb4e79408..ea07d32bfff1d0 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -22,7 +22,8 @@ export function resultsServiceProvider( latestMs: number, intervalMs: number, perPage?: number, - fromPage?: number + fromPage?: number, + swimLaneSeverity?: number ): Promise; getTopInfluencers( selectedJobIds: string[], @@ -40,7 +41,8 @@ export function resultsServiceProvider( topN: any, earliestMs: any, latestMs: any, - interval?: any + interval?: any, + overallScore?: number ): Promise; getInfluencerValueMaxScoreByTime( jobIds: string[], @@ -52,7 +54,8 @@ export function resultsServiceProvider( maxResults: number, perPage: number, fromPage: number, - influencersFilterQuery: InfluencersFilterQuery + influencersFilterQuery: InfluencersFilterQuery, + swimLaneSeverity?: number ): Promise; getRecordInfluencers(): Promise; getRecordsForDetector(): Promise; diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.js b/x-pack/plugins/ml/public/application/services/results_service/results_service.js index 71be7bcd2b7eb7..bb6f6b5969ac4d 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.js +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.js @@ -30,7 +30,15 @@ export function resultsServiceProvider(mlApiServices) { // Pass an empty array or ['*'] to search over all job IDs. // Returned response contains a results property, with a key for job // which has results for the specified time range. - getScoresByBucket(jobIds, earliestMs, latestMs, intervalMs, perPage = 10, fromPage = 1) { + getScoresByBucket( + jobIds, + earliestMs, + latestMs, + intervalMs, + perPage = 10, + fromPage = 1, + swimLaneSeverity = 0 + ) { return new Promise((resolve, reject) => { const obj = { success: true, @@ -49,6 +57,13 @@ export function resultsServiceProvider(mlApiServices) { }, }, }, + { + range: { + anomaly_score: { + gt: swimLaneSeverity, + }, + }, + }, ]; if (jobIds && jobIds.length > 0 && !(jobIds.length === 1 && jobIds[0] === '*')) { @@ -463,7 +478,7 @@ export function resultsServiceProvider(mlApiServices) { // Obtains the overall bucket scores for the specified job ID(s). // Pass ['*'] to search over all job IDs. // Returned response contains a results property as an object of max score by time. - getOverallBucketScores(jobIds, topN, earliestMs, latestMs, interval) { + getOverallBucketScores(jobIds, topN, earliestMs, latestMs, interval, overallScore) { return new Promise((resolve, reject) => { const obj = { success: true, results: {} }; @@ -474,6 +489,7 @@ export function resultsServiceProvider(mlApiServices) { bucketSpan: interval, start: earliestMs, end: latestMs, + overallScore, }) .then((resp) => { const dataByTime = get(resp, ['overall_buckets'], []); @@ -507,7 +523,8 @@ export function resultsServiceProvider(mlApiServices) { maxResults = ANOMALY_SWIM_LANE_HARD_LIMIT, perPage = SWIM_LANE_DEFAULT_PAGE_SIZE, fromPage = 1, - influencersFilterQuery + influencersFilterQuery, + swimLaneSeverity ) { return new Promise((resolve, reject) => { const obj = { success: true, results: {} }; @@ -527,7 +544,7 @@ export function resultsServiceProvider(mlApiServices) { { range: { influencer_score: { - gt: 0, + gt: swimLaneSeverity !== undefined ? swimLaneSeverity : 0, }, }, }, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss index 33f6c65e03e77f..cfd521c882fb71 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss @@ -19,10 +19,6 @@ float: right; } - .ml-anomalies-controls { - padding-top: $euiSizeXS; - } - .ml-timeseries-chart { svg { font-size: $euiFontSizeXS; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index c33b780631f166..c2b806abcf2860 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -26,11 +26,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiIcon, EuiSpacer, EuiPanel, EuiTitle, - EuiToolTip, EuiAccordion, EuiBadge, } from '@elastic/eui'; @@ -1273,41 +1271,12 @@ export class TimeSeriesExplorer extends React.Component { /> - - - - - + + + - - - - {i18n.translate('xpack.ml.timeSeriesExplorer.intervalLabel', { - defaultMessage: 'Interval', - })} - - - - } - > - - + + diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index c9fde252fc26dd..1f41f0a1d25c30 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -53,6 +53,7 @@ import { } from '../../triggers_actions_ui/public'; import { FileDataVisualizerPluginStart } from '../../file_data_visualizer/public'; import { PluginSetupContract as AlertingSetup } from '../../alerting/public'; +import { registerManagementSection } from './application/management'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -133,6 +134,10 @@ export class MlPlugin implements Plugin { this.urlGenerator = registerUrlGenerator(pluginsSetup.share, core); } + if (pluginsSetup.management) { + registerManagementSection(pluginsSetup.management, core).enable(); + } + const licensing = pluginsSetup.licensing.license$.pipe(take(1)); licensing.subscribe(async (license) => { const [coreStart] = await core.getStartServices(); @@ -160,7 +165,6 @@ export class MlPlugin implements Plugin { // note including registerFeature in register_helper would cause the page bundle size to increase significantly const { registerEmbeddables, - registerManagementSection, registerMlUiActions, registerSearchLinks, registerMlAlerts, @@ -172,11 +176,6 @@ export class MlPlugin implements Plugin { registerSearchLinks(this.appUpdater$, fullLicense); if (fullLicense) { - const canManageMLJobs = - capabilities.management?.insightsAndAlerting?.jobsListLink ?? false; - if (canManageMLJobs && pluginsSetup.management !== undefined) { - registerManagementSection(pluginsSetup.management, core).enable(); - } registerEmbeddables(pluginsSetup.embeddable, core); registerMlUiActions(pluginsSetup.uiActions, core); diff --git a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts index 6adf6fa474cad0..5205ea7353ac61 100644 --- a/x-pack/plugins/ml/server/routes/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts @@ -522,6 +522,7 @@ export function jobRoutes({ router, routeGuard }: RouteInitialization) { bucket_span: request.body.bucketSpan, start: request.body.start !== undefined ? String(request.body.start) : undefined, end: request.body.end !== undefined ? String(request.body.end) : undefined, + overall_score: request.body.overall_score ?? 0, }, }); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index 4217002e61ef72..392c0d3514d648 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -186,6 +186,7 @@ export const getOverallBucketsSchema = schema.object({ bucketSpan: schema.string(), start: schema.number(), end: schema.number(), + overall_score: schema.maybe(schema.number()), }); export const getCategoriesSchema = schema.object({ diff --git a/x-pack/plugins/observability/public/components/shared/header_menu_portal.test.tsx b/x-pack/plugins/observability/public/components/shared/header_menu_portal.test.tsx new file mode 100644 index 00000000000000..4e9a1ae2c587f0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/header_menu_portal.test.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import HeaderMenuPortal from './header_menu_portal'; + +describe('HeaderMenuPortal', () => { + describe('when unmounted', () => { + it('calls setHeaderActionMenu with undefined', () => { + const setHeaderActionMenu = jest.fn(); + + const { unmount } = render( + test + ); + + unmount(); + + expect(setHeaderActionMenu).toHaveBeenCalledWith(undefined); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/header_menu_portal.tsx b/x-pack/plugins/observability/public/components/shared/header_menu_portal.tsx index 54949c1d860d83..6c3b558c5e61df 100644 --- a/x-pack/plugins/observability/public/components/shared/header_menu_portal.tsx +++ b/x-pack/plugins/observability/public/components/shared/header_menu_portal.tsx @@ -15,17 +15,14 @@ export default function HeaderMenuPortal({ children, setHeaderActionMenu }: Head const portalNode = useMemo(() => createPortalNode(), []); useEffect(() => { - let unmount = () => {}; - setHeaderActionMenu((element) => { const mount = toMountPoint(); - unmount = mount(element); - return unmount; + return mount(element); }); return () => { portalNode.unmount(); - unmount(); + setHeaderActionMenu(undefined); }; }, [portalNode, setHeaderActionMenu]); diff --git a/x-pack/plugins/observability/public/components/shared/page_template/README.md b/x-pack/plugins/observability/public/components/shared/page_template/README.md index e360e6d95a9d8a..fb2a603cc7a7fa 100644 --- a/x-pack/plugins/observability/public/components/shared/page_template/README.md +++ b/x-pack/plugins/observability/public/components/shared/page_template/README.md @@ -17,6 +17,8 @@ Now within your solution's **public** plugin `setup` lifecycle method you can ca ```typescript // x-pack/plugins/example_plugin/public/plugin.ts +import { of } from 'rxjs'; + export class Plugin implements PluginClass { constructor(_context: PluginInitializerContext) {} @@ -64,7 +66,7 @@ This can be accessed like so: ``` const [coreStart, pluginsStart] = await core.getStartServices(); -const pageTemplateComponent = pluginsStart.observability.navigation.PageTemplate; +const ObservabilityPageTemplate = pluginsStart.observability.navigation.PageTemplate; ``` Now that you have access to the component you can render your solution's content using it. @@ -101,4 +103,4 @@ The `` component is a wrapper around the ` '' } }, }, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), + ObservabilityPageTemplate: KibanaPageTemplate, } as unknown) as PluginContextValue } > diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md index cfbde612b45a6d..e12c2b29ed3738 100644 --- a/x-pack/plugins/rule_registry/README.md +++ b/x-pack/plugins/rule_registry/README.md @@ -145,3 +145,6 @@ The following fields are defined in the technical field component template and s - `kibana.rac.alert.severity.value`: the severity of the alert, as a numerical value, which allows sorting. - `kibana.rac.alert.evaluation.value`: The measured (numerical value). - `kibana.rac.alert.threshold.value`: The threshold that was defined (or, in case of multiple thresholds, the one that was exceeded). +- `kibana.rac.alert.ancestors`: the array of ancestors (if any) for the alert. +- `kibana.rac.alert.depth`: the depth of the alert in the ancestral tree (default 0). +- `kibana.rac.alert.building_block_type`: the building block type of the alert (default undefined). diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts index 9547f165cd7058..9eefc19f34670e 100644 --- a/x-pack/plugins/rule_registry/server/index.ts +++ b/x-pack/plugins/rule_registry/server/index.ts @@ -14,6 +14,7 @@ export { RuleDataClient } from './rule_data_client'; export { IRuleDataClient } from './rule_data_client/types'; export { getRuleExecutorData, RuleExecutorData } from './utils/get_rule_executor_data'; export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory'; +export { createPersistenceRuleTypeFactory } from './utils/create_persistence_rule_type_factory'; export const plugin = (initContext: PluginInitializerContext) => new RuleRegistryPlugin(initContext); diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts index 135c870f207272..43122ba49519a4 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/index.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/index.ts @@ -73,8 +73,8 @@ export class RuleDataClient implements IRuleDataClient { return clusterClient.bulk(requestWithDefaultParameters).then((response) => { if (response.body.errors) { if ( - response.body.items.length === 1 && - response.body.items[0]?.index?.error?.type === 'index_not_found_exception' + response.body.items.length > 0 && + response.body.items?.[0]?.index?.error?.type === 'index_not_found_exception' ) { return this.createOrUpdateWriteTarget({ namespace }).then(() => { return clusterClient.bulk(requestWithDefaultParameters); diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts new file mode 100644 index 00000000000000..0e244fbaa2ee35 --- /dev/null +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ESSearchRequest } from 'typings/elasticsearch'; +import v4 from 'uuid/v4'; +import { Logger } from '@kbn/logging'; + +import { AlertInstance } from '../../../alerting/server'; +import { + AlertInstanceContext, + AlertInstanceState, + AlertTypeParams, +} from '../../../alerting/common'; +import { RuleDataClient } from '../rule_data_client'; +import { AlertTypeWithExecutor } from '../types'; + +type PersistenceAlertService> = ( + alerts: Array> +) => Array>; + +type PersistenceAlertQueryService = ( + query: ESSearchRequest +) => Promise>>; + +type CreatePersistenceRuleTypeFactory = (options: { + ruleDataClient: RuleDataClient; + logger: Logger; +}) => < + TParams extends AlertTypeParams, + TAlertInstanceContext extends AlertInstanceContext, + TServices extends { + alertWithPersistence: PersistenceAlertService; + findAlerts: PersistenceAlertQueryService; + } +>( + type: AlertTypeWithExecutor +) => AlertTypeWithExecutor; + +export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory = ({ + logger, + ruleDataClient, +}) => (type) => { + return { + ...type, + executor: async (options) => { + const { + services: { alertInstanceFactory, scopedClusterClient }, + } = options; + + const currentAlerts: Array> = []; + const timestamp = options.startedAt.toISOString(); + + const state = await type.executor({ + ...options, + services: { + ...options.services, + alertWithPersistence: (alerts) => { + alerts.forEach((alert) => currentAlerts.push(alert)); + return alerts.map((alert) => + alertInstanceFactory(alert['kibana.rac.alert.uuid']! as string) + ); + }, + findAlerts: async (query) => { + const { body } = await scopedClusterClient.asCurrentUser.search({ + ...query, + body: { + ...query.body, + }, + ignore_unavailable: true, + }); + return body.hits.hits + .map((event: { _source: any }) => event._source!) + .map((event: { [x: string]: any }) => { + const alertUuid = event['kibana.rac.alert.uuid']; + const isAlert = alertUuid != null; + return { + ...event, + 'event.kind': 'signal', + 'kibana.rac.alert.id': '???', + 'kibana.rac.alert.status': 'open', + 'kibana.rac.alert.uuid': v4(), + 'kibana.rac.alert.ancestors': isAlert + ? ((event['kibana.rac.alert.ancestors'] as string[]) ?? []).concat([ + alertUuid!, + ] as string[]) + : [], + 'kibana.rac.alert.depth': isAlert + ? ((event['kibana.rac.alert.depth'] as number) ?? 0) + 1 + : 0, + '@timestamp': timestamp, + }; + }); + }, + }, + }); + + const numAlerts = currentAlerts.length; + logger.debug(`Found ${numAlerts} alerts.`); + + if (ruleDataClient && numAlerts) { + await ruleDataClient.getWriter().bulk({ + body: currentAlerts.flatMap((event) => [{ index: {} }, event]), + }); + } + + return state; + }, + }; +}; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index effefdd438c5cc..91b48afdc4ed1e 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -25,6 +25,7 @@ export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults'; export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults'; export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults'; export const DEFAULT_APP_REFRESH_INTERVAL = 'securitySolution:refreshIntervalDefaults'; +export const DEFAULT_ALERTS_INDEX = '.alerts-security-solution'; export const DEFAULT_SIGNALS_INDEX = '.siem-signals'; export const DEFAULT_LISTS_INDEX = '.lists'; export const DEFAULT_ITEMS_INDEX = '.items'; @@ -148,6 +149,18 @@ export const DEFAULT_TRANSFORMS_SETTING = JSON.stringify(defaultTransformsSettin */ export const SIGNALS_ID = `siem.signals`; +/** + * Id's for reference rule types + */ +export const REFERENCE_RULE_ALERT_TYPE_ID = `siem.referenceRule`; +export const REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID = `siem.referenceRulePersistence`; + +export const CUSTOM_ALERT_TYPE_ID = `siem.customRule`; +export const EQL_ALERT_TYPE_ID = `siem.eqlRule`; +export const INDICATOR_ALERT_TYPE_ID = `siem.indicatorRule`; +export const ML_ALERT_TYPE_ID = `siem.mlRule`; +export const THRESHOLD_ALERT_TYPE_ID = `siem.thresholdRule`; + /** * Id for the notifications alerting type */ diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts index c0888a6c2a4bd4..35c976fbdfb1d1 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts @@ -9,6 +9,8 @@ import seedrandom from 'seedrandom'; import uuid from 'uuid'; const OS_FAMILY = ['windows', 'macos', 'linux']; +/** Array of 14 day offsets */ +const DAY_OFFSETS = Array.from({ length: 14 }, (_, i) => 8.64e7 * (i + 1)); /** * A generic base class to assist in creating domain specific data generators. It includes @@ -16,6 +18,7 @@ const OS_FAMILY = ['windows', 'macos', 'linux']; * public method named `generate()` which should be implemented by sub-classes. */ export class BaseDataGenerator { + /** A javascript seeded random number (float between 0 and 1). Don't use `Math.random()` */ protected random: seedrandom.prng; constructor(seed: string | seedrandom.prng = Math.random().toString()) { @@ -33,6 +36,23 @@ export class BaseDataGenerator { throw new Error('method not implemented!'); } + /** Returns a future ISO date string */ + protected randomFutureDate(from?: Date): string { + const now = from ? from.getTime() : Date.now(); + return new Date(now + this.randomChoice(DAY_OFFSETS)).toISOString(); + } + + /** Returns a past ISO date string */ + protected randomPastDate(from?: Date): string { + const now = from ? from.getTime() : Date.now(); + return new Date(now - this.randomChoice(DAY_OFFSETS)).toISOString(); + } + + /** Generate either `true` or `false` */ + protected randomBoolean(): boolean { + return this.random() < 0.5; + } + /** generate random OS family value */ protected randomOSFamily(): string { return this.randomChoice(OS_FAMILY); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts new file mode 100644 index 00000000000000..af799de782f48c --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; +import { BaseDataGenerator } from './base_data_generator'; +import { EndpointAction, EndpointActionResponse, ISOLATION_ACTIONS } from '../types'; + +const ISOLATION_COMMANDS: ISOLATION_ACTIONS[] = ['isolate', 'unisolate']; + +export class FleetActionGenerator extends BaseDataGenerator { + /** Generate an Action */ + generate(overrides: DeepPartial = {}): EndpointAction { + const timeStamp = new Date(this.randomPastDate()); + + return merge( + { + action_id: this.randomUUID(), + '@timestamp': timeStamp.toISOString(), + expiration: this.randomFutureDate(timeStamp), + type: 'INPUT_ACTION', + input_type: 'endpoint', + agents: [this.randomUUID()], + user_id: 'elastic', + data: { + command: this.randomIsolateCommand(), + comment: this.randomString(15), + }, + }, + overrides + ); + } + + /** Generates an action response */ + generateResponse(overrides: DeepPartial = {}): EndpointActionResponse { + const timeStamp = new Date(); + + return merge( + { + action_data: { + command: this.randomIsolateCommand(), + comment: '', + }, + action_id: this.randomUUID(), + agent_id: this.randomUUID(), + started_at: this.randomPastDate(), + completed_at: timeStamp.toISOString(), + error: 'some error happen', + '@timestamp': timeStamp.toISOString(), + }, + overrides + ); + } + + protected randomIsolateCommand() { + return this.randomChoice(ISOLATION_COMMANDS); + } +} diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts index e29a121668bd3b..301a032fb47dfb 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.test.ts @@ -87,7 +87,9 @@ describe('data generator', () => { expect(event2.event?.sequence).toBe((firstNonNullValue(event1.event?.sequence) ?? 0) + 1); }); - it('creates the same documents with same random seed', () => { + // Lets run this one multiple times just to ensure that the randomness + // is truly predicable based on the seed passed + it.each([1, 2, 3, 4, 5])('[%#] creates the same documents with same random seed', () => { const generator1 = new EndpointDocGenerator('seed'); const generator2 = new EndpointDocGenerator('seed'); const timestamp = new Date().getTime(); diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index fa7ee84441a9bc..436f1573639c83 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -439,6 +439,8 @@ export class EndpointDocGenerator extends BaseDataGenerator { private createHostData(): HostInfo { const hostName = this.randomHostname(); + const isIsolated = this.randomBoolean(); + return { agent: { version: this.randomVersion(), @@ -465,10 +467,10 @@ export class EndpointDocGenerator extends BaseDataGenerator { applied: this.randomChoice(APPLIED_POLICIES), }, configuration: { - isolation: false, + isolation: isIsolated, }, state: { - isolation: false, + isolation: isIsolated, }, }, }; diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts index 0dc7891560c2d8..021b9bcb1ecccf 100644 --- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts @@ -28,8 +28,10 @@ import { policyFactory as policyConfigFactory } from './models/policy_config'; import { HostMetadata } from './types'; import { KbnClientWithApiKeySupport } from '../../scripts/endpoint/kbn_client_with_api_key_support'; import { FleetAgentGenerator } from './data_generators/fleet_agent_generator'; +import { FleetActionGenerator } from './data_generators/fleet_action_generator'; const fleetAgentGenerator = new FleetAgentGenerator(); +const fleetActionGenerator = new FleetActionGenerator(); export async function indexHostsAndAlerts( client: Client, @@ -175,6 +177,9 @@ async function indexHostDocs({ }, }, }; + + // Create some actions for this Host + await indexFleetActionsForHost(client, hostMetadata); } await client.index({ @@ -397,3 +402,43 @@ const indexFleetAgentForHost = async ( return agentDoc; }; + +const indexFleetActionsForHost = async ( + esClient: Client, + endpointHost: HostMetadata +): Promise => { + const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; + const agentId = endpointHost.elastic.agent.id; + + for (let i = 0; i < 5; i++) { + // create an action + const isolateAction = fleetActionGenerator.generate({ + data: { comment: 'data generator: this host is bad' }, + }); + + isolateAction.agents = [agentId]; + + await esClient.index( + { + index: '.fleet-actions', + body: isolateAction, + }, + ES_INDEX_OPTIONS + ); + + // Create an action response for the above + const unIsolateAction = fleetActionGenerator.generateResponse({ + action_id: isolateAction.action_id, + agent_id: agentId, + action_data: isolateAction.data, + }); + + await esClient.index( + { + index: '.fleet-actions-results', + body: unIsolateAction, + }, + ES_INDEX_OPTIONS + ); + } +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 99dac5ea5cda67..fcfda9c9a30d94 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -24,6 +24,21 @@ export interface EndpointAction { }; } +export interface EndpointActionResponse { + '@timestamp': string; + /** The id of the action for which this response is associated with */ + action_id: string; + /** The agent id that sent this action response */ + agent_id: string; + started_at: string; + completed_at: string; + error: string; + action_data: { + command: ISOLATION_ACTIONS; + comment?: string; + }; +} + export type HostIsolationRequestBody = TypeOf; export interface HostIsolationResponse { diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 6195dd61a79841..02006fdb29d475 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -15,6 +15,7 @@ const allowedExperimentalValues = Object.freeze({ trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, hostIsolationEnabled: false, + ruleRegistryEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 50a5f62740271f..02dbc56bd33976 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -8,6 +8,7 @@ "actions", "alerting", "cases", + "ruleRegistry", "data", "dataEnhanced", "embeddable", diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx index 3d29650b750dc9..e4a015525dfb44 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx @@ -21,7 +21,7 @@ import type { CreateExceptionListItemSchema, UpdateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; - +import { TestProviders } from '../../mock'; import { useAddOrUpdateException, UseAddOrUpdateExceptionProps, @@ -134,12 +134,16 @@ describe('useAddOrUpdateException', () => { addOrUpdateItemsArgs = [ruleId, itemsToAddOrUpdate]; render = () => - renderHook(() => - useAddOrUpdateException({ - http: mockKibanaHttpService, - onError, - onSuccess, - }) + renderHook( + () => + useAddOrUpdateException({ + http: mockKibanaHttpService, + onError, + onSuccess, + }), + { + wrapper: TestProviders, + } ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx index 5ba73ba2c90588..dbae0964b41a8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx @@ -19,9 +19,11 @@ import { getUpdateAlertsQuery } from '../../../detections/components/alerts_tabl import { buildAlertStatusFilter, buildAlertsRuleIdFilter, + buildAlertStatusFilterRuleRegistry, } from '../../../detections/components/alerts_table/default_config'; import { getQueryFilter } from '../../../../common/detection_engine/get_query_filter'; import { Index } from '../../../../common/detection_engine/schemas/common/schemas'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { formatExceptionItemForUpdate, prepareExceptionItemsForBulkClose } from './helpers'; import { useKibana } from '../../lib/kibana'; @@ -82,6 +84,8 @@ export const useAddOrUpdateException = ({ }, [] ); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); useEffect(() => { let isSubscribed = true; @@ -127,10 +131,15 @@ export const useAddOrUpdateException = ({ } if (bulkCloseIndex != null) { + // TODO: Once we are past experimental phase this code should be removed + const alertStatusFilter = ruleRegistryEnabled + ? buildAlertStatusFilterRuleRegistry('open') + : buildAlertStatusFilter('open'); + const filter = getQueryFilter( '', 'kuery', - [...buildAlertsRuleIdFilter(ruleId), ...buildAlertStatusFilter('open')], + [...buildAlertsRuleIdFilter(ruleId), ...alertStatusFilter], bulkCloseIndex, prepareExceptionItemsForBulkClose(exceptionItemsToAddOrUpdate), false @@ -176,7 +185,14 @@ export const useAddOrUpdateException = ({ isSubscribed = false; abortCtrl.abort(); }; - }, [http, onSuccess, onError, updateExceptionListItem, addExceptionListItem]); + }, [ + addExceptionListItem, + http, + onSuccess, + onError, + ruleRegistryEnabled, + updateExceptionListItem, + ]); return [{ isLoading }, addOrUpdateException]; }; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index af278b09e719c4..71e33c603b65b2 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -43,6 +43,7 @@ export const mockGlobalState: State = { trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, hostIsolationEnabled: false, + ruleRegistryEnabled: false, }, }, hosts: { diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 90526e84a2262b..9ac7ae0f243225 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -24,11 +24,12 @@ import { import { FieldHook } from '../../shared_imports'; import { SUB_PLUGINS_REDUCER } from './utils'; import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage'; +import { UserPrivilegesProvider } from '../../detections/components/user_privileges'; const state: State = mockGlobalState; interface Props { - children: React.ReactNode; + children?: React.ReactNode; store?: Store; onDragEnd?: (result: DropResult, provided: ResponderProvided) => void; } @@ -59,7 +60,30 @@ const TestProvidersComponent: React.FC = ({ ); +/** + * A utility for wrapping children in the providers required to run most tests + * WITH user privileges provider. + */ +const TestProvidersWithPrivilegesComponent: React.FC = ({ + children, + store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage), + onDragEnd = jest.fn(), +}) => ( + + + + ({ eui: euiDarkVars, darkMode: true })}> + + {children} + + + + + +); + export const TestProviders = React.memo(TestProvidersComponent); +export const TestProvidersWithPrivileges = React.memo(TestProvidersWithPrivilegesComponent); export const useFormFieldMock = (options?: Partial>): FieldHook => { return { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 478c8930b8dd3b..02a815bc59f3bd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -5,11 +5,12 @@ * 2.0. */ +import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { RowRendererId } from '../../../../common/types/timeline'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; -import { SubsetTimelineModel } from '../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { columns } from '../../configurations/security_solution_detections/columns'; @@ -124,3 +125,76 @@ export const requiredFieldsForActions = [ 'host.os.family', 'event.code', ]; + +// TODO: Once we are past experimental phase this code should be removed +export const buildAlertStatusFilterRuleRegistry = (status: Status): Filter[] => [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'kibana.rac.alert.status', + params: { + query: status, + }, + }, + query: { + term: { + 'kibana.rac.alert.status': status, + }, + }, + }, +]; + +export const buildShowBuildingBlockFilterRuleRegistry = ( + showBuildingBlockAlerts: boolean +): Filter[] => + showBuildingBlockAlerts + ? [] + : [ + { + meta: { + alias: null, + negate: true, + disabled: false, + type: 'exists', + key: 'kibana.rac.rule.building_block_type', + value: 'exists', + }, + // @ts-expect-error TODO: Rework parent typings to support ExistsFilter[] + exists: { field: 'kibana.rac.rule.building_block_type' }, + }, + ]; + +export const requiredFieldMappingsForActionsRuleRegistry = { + '@timestamp': '@timestamp', + 'alert.id': 'kibana.rac.alert.id', + 'event.kind': 'event.kind', + 'alert.start': 'kibana.rac.alert.start', + 'alert.uuid': 'kibana.rac.alert.uuid', + 'event.action': 'event.action', + 'alert.status': 'kibana.rac.alert.status', + 'alert.duration.us': 'kibana.rac.alert.duration.us', + 'rule.uuid': 'rule.uuid', + 'rule.id': 'rule.id', + 'rule.name': 'rule.name', + 'rule.category': 'rule.category', + producer: 'kibana.rac.alert.producer', + tags: 'tags', +}; + +export const alertsHeadersRuleRegistry: ColumnHeaderOptions[] = Object.entries( + requiredFieldMappingsForActionsRuleRegistry +).map(([alias, field]) => ({ + columnHeaderType: defaultColumnHeaderType, + displayAsText: alias, + id: field, +})); + +export const alertsDefaultModelRuleRegistry: SubsetTimelineModel = { + ...timelineDefaults, + columns: alertsHeadersRuleRegistry, + showCheckboxes: true, + excludedRowRendererIds: Object.values(RowRendererId), +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 9dc83d7898963d..f20754fc446d6e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -16,6 +16,7 @@ import { TimelineIdLiteral } from '../../../../common/types/timeline'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HeaderSection } from '../../../common/components/header_section'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { combineQueries } from '../../../timelines/components/timeline/helpers'; import { useKibana } from '../../../common/lib/kibana'; import { inputsSelectors, State, inputsModel } from '../../../common/store'; @@ -29,6 +30,8 @@ import { requiredFieldsForActions, alertsDefaultModel, buildAlertStatusFilter, + alertsDefaultModelRuleRegistry, + buildAlertStatusFilterRuleRegistry, } from './default_config'; import { FILTER_OPEN, AlertsTableFilterGroup } from './alerts_filter_group'; import { AlertsUtilityBar } from './alerts_utility_bar'; @@ -104,6 +107,8 @@ export const AlertsTableComponent: React.FC = ({ const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); const { initializeTimeline, setSelectAll } = useManageTimeline(); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -236,7 +241,11 @@ export const AlertsTableComponent: React.FC = ({ refetchQuery: inputsModel.Refetch, { status, selectedStatus }: UpdateAlertsStatusProps ) => { - const currentStatusFilter = buildAlertStatusFilter(status); + // TODO: Once we are past experimental phase this code should be removed + const currentStatusFilter = ruleRegistryEnabled + ? buildAlertStatusFilterRuleRegistry(status) + : buildAlertStatusFilter(status); + await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery(currentStatusFilter)?.filterQuery @@ -258,6 +267,7 @@ export const AlertsTableComponent: React.FC = ({ showClearSelectionAction, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, + ruleRegistryEnabled, ] ); @@ -301,18 +311,28 @@ export const AlertsTableComponent: React.FC = ({ ); const defaultFiltersMemo = useMemo(() => { + // TODO: Once we are past experimental phase this code should be removed + const alertStatusFilter = ruleRegistryEnabled + ? buildAlertStatusFilterRuleRegistry(filterGroup) + : buildAlertStatusFilter(filterGroup); + if (isEmpty(defaultFilters)) { - return buildAlertStatusFilter(filterGroup); + return alertStatusFilter; } else if (defaultFilters != null && !isEmpty(defaultFilters)) { - return [...defaultFilters, ...buildAlertStatusFilter(filterGroup)]; + return [...defaultFilters, ...alertStatusFilter]; } - }, [defaultFilters, filterGroup]); + }, [defaultFilters, filterGroup, ruleRegistryEnabled]); const { filterManager } = useKibana().services.data.query; + // TODO: Once we are past experimental phase this code should be removed + const defaultTimelineModel = ruleRegistryEnabled + ? alertsDefaultModelRuleRegistry + : alertsDefaultModel; + useEffect(() => { initializeTimeline({ defaultModel: { - ...alertsDefaultModel, + ...defaultTimelineModel, columns, }, documentType: i18n.ALERTS_DOCUMENT_TYPE, @@ -344,7 +364,7 @@ export const AlertsTableComponent: React.FC = ({ return ( ( - {children} -); - describe('useSignalIndex', () => { let appToastsMock: jest.Mocked>; @@ -33,7 +28,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); expect(result.current).toEqual({ @@ -50,7 +47,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -69,7 +68,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -93,7 +94,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -114,7 +117,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); await waitForNextUpdate(); @@ -140,7 +145,9 @@ describe('useSignalIndex', () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useSignalIndex(), - { wrapper: Wrapper } + { + wrapper: TestProvidersWithPrivileges, + } ); await waitForNextUpdate(); await waitForNextUpdate(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx index fdbeab26f11f3a..84eaf8e3aa93c3 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx @@ -6,8 +6,10 @@ */ import { useEffect, useState } from 'react'; +import { DEFAULT_ALERTS_INDEX } from '../../../../../common/constants'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { isSecurityAppError } from '../../../../common/utils/api'; @@ -38,6 +40,8 @@ export const useSignalIndex = (): ReturnSignalIndex => { }); const { addError } = useAppToasts(); const { hasIndexRead } = useAlertsPrivileges(); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); useEffect(() => { let isSubscribed = true; @@ -48,10 +52,15 @@ export const useSignalIndex = (): ReturnSignalIndex => { setLoading(true); const signal = await getSignalIndex({ signal: abortCtrl.signal }); + // TODO: Once we are past experimental phase we can update `getSignalIndex` to return the space-aware DEFAULT_ALERTS_INDEX + const signalIndices = ruleRegistryEnabled + ? `${DEFAULT_ALERTS_INDEX},${signal.name}` + : signal.name; + if (isSubscribed && signal != null) { setSignalIndex({ signalIndexExists: true, - signalIndexName: signal.name, + signalIndexName: signalIndices, signalIndexMappingOutdated: signal.index_mapping_outdated, createDeSignalIndex: createIndex, }); @@ -115,7 +124,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { isSubscribed = false; abortCtrl.abort(); }; - }, [addError, hasIndexRead]); + }, [addError, hasIndexRead, ruleRegistryEnabled]); return { loading, ...signalIndex }; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index c1c7e4688bbbe3..8ae7e4fb2852b5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -11,6 +11,7 @@ import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { SecurityPageName } from '../../../app/types'; @@ -51,6 +52,7 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { buildShowBuildingBlockFilter, + buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, } from '../../components/alerts_table/default_config'; import { useSourcererScope } from '../../../common/containers/sourcerer'; @@ -81,6 +83,8 @@ const DetectionEnginePageComponent = () => { const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); const { to, from, deleteQuery, setQuery } = useGlobalTime(); const { globalFullScreen } = useGlobalFullScreen(); @@ -134,19 +138,23 @@ const DetectionEnginePageComponent = () => { const alertsHistogramDefaultFilters = useMemo( () => [ ...filters, - ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...(ruleRegistryEnabled + ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed + : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [filters, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [filters, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); // AlertsTable manages global filters itself, so not including `filters` const alertsTableDefaultFilters = useMemo( () => [ - ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...(ruleRegistryEnabled + ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed + : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); const onShowBuildingBlockAlertsChangedCallback = useCallback( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index d3793dad8ff1a4..8dac9e03514d1c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -36,6 +36,7 @@ import { useDeepEqualSelector, useShallowEqualSelector, } from '../../../../../common/hooks/use_selector'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { useKibana } from '../../../../../common/lib/kibana'; import { TimelineId } from '../../../../../../common/types/timeline'; import { UpdateDateRange } from '../../../../../common/components/charts/common'; @@ -64,6 +65,7 @@ import { StepScheduleRule } from '../../../../components/rules/step_schedule_rul import { buildAlertsRuleIdFilter, buildShowBuildingBlockFilter, + buildShowBuildingBlockFilterRuleRegistry, buildThreatMatchFilter, } from '../../../../components/alerts_table/default_config'; import { RuleSwitch } from '../../../../components/rules/rule_switch'; @@ -222,6 +224,9 @@ const RuleDetailsPageComponent = () => { const { formatUrl } = useFormatUrl(SecurityPageName.detections); const { globalFullScreen } = useGlobalFullScreen(); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); + // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); const { @@ -307,10 +312,12 @@ const RuleDetailsPageComponent = () => { const alertDefaultFilters = useMemo( () => [ ...buildAlertsRuleIdFilter(ruleId), - ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...(ruleRegistryEnabled + ? buildShowBuildingBlockFilterRuleRegistry(showBuildingBlockAlerts) // TODO: Once we are past experimental phase this code should be removed + : buildShowBuildingBlockFilter(showBuildingBlockAlerts)), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [ruleId, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [ruleId, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx index 4871cfcb069d2f..f1eab38c56db0a 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx @@ -155,7 +155,7 @@ describe('Hosts - rendering', () => { myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); wrapper.update(); expect(wrapper.find(HostsTabs).props().filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' ); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/management/components/search_bar/index.tsx index 3c92ab31680c20..5ace2b901da11c 100644 --- a/x-pack/plugins/security_solution/public/management/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/management/components/search_bar/index.tsx @@ -25,7 +25,7 @@ export const SearchBar = memo(({ defaultValue = '', onSearch, pl const handleOnSearch = useCallback(() => onSearch(query), [query, onSearch]); return ( - + } /> + + } + /> + + } + /> } /> + + } + /> + + } + /> ( } /> + + } + /> + + } + /> +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -762,6 +782,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -1031,6 +1071,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -1300,6 +1360,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -1569,6 +1649,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -1838,6 +1938,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -2107,6 +2227,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -2376,6 +2516,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -2645,6 +2805,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -2914,6 +3094,26 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -3488,6 +3688,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -3757,6 +3977,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -4026,6 +4266,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -4295,6 +4555,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -4564,6 +4844,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -4833,6 +5133,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -5102,6 +5422,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -5371,6 +5711,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -5640,6 +6000,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -5909,6 +6289,26 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -6440,6 +6840,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -6709,6 +7129,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -6978,6 +7418,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -7247,6 +7707,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -7516,6 +7996,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -7785,6 +8285,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -8054,6 +8574,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -8323,6 +8863,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -8592,6 +9152,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
@@ -8861,6 +9441,26 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap index 15f35705c7fdc3..c4f2a74ea173df 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap @@ -842,6 +842,26 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` +
+ Date Modified +
+
+ 1 minute ago +
+
+ Modified By +
+
+ someone +
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index 4dfe1d96a56c15..803e292c58eb5a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -77,10 +77,10 @@ export const PROPERTY_TITLES: Readonly< defaultMessage: 'Created By', }), updated_at: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.updatedAt', { - defaultMessage: 'Date Updated', + defaultMessage: 'Date Modified', }), updated_by: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.updatedBy', { - defaultMessage: 'Updated By', + defaultMessage: 'Modified By', }), description: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.description', { defaultMessage: 'Description', diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 874d1a4a969c4c..3f02d505daea1e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -169,10 +169,15 @@ describe('When on the Trusted Apps Page', () => { it('should display a Add Trusted App button', async () => { const { getByTestId } = await renderWithListData(); - const addButton = await getByTestId('trustedAppsListAddButton'); + const addButton = getByTestId('trustedAppsListAddButton'); expect(addButton.textContent).toBe('Add Trusted Application'); }); + it('should display the searchbar', async () => { + const renderResult = await renderWithListData(); + expect(await renderResult.findByTestId('searchBar')).not.toBeNull(); + }); + describe('and the Grid view is being displayed', () => { describe('and the edit trusted app button is clicked', () => { let renderResult: ReturnType; @@ -555,7 +560,7 @@ describe('When on the Trusted Apps Page', () => { // to test the UI behaviours while the API call is in flight coreStart.http.post.mockImplementation( // @ts-ignore - async (path: string, options: HttpFetchOptions) => { + async (_, options: HttpFetchOptions) => { return new Promise((resolve, reject) => { httpPostBody = options.body as string; resolveHttpPost = resolve; @@ -861,6 +866,14 @@ describe('When on the Trusted Apps Page', () => { expect(await renderResult.findByTestId('trustedAppEmptyState')).not.toBeNull(); }); + + it('should not display the searchbar', async () => { + const renderResult = render(); + await act(async () => { + await waitForAction('trustedAppsExistStateChanged'); + }); + expect(renderResult.queryByTestId('searchBar')).toBeNull(); + }); }); describe('and the search is dispatched', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx index ac06254a531001..5603b8e2d61c90 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.tsx @@ -96,34 +96,36 @@ export const TrustedAppsPage = memo(() => { /> )} - {doEntriesExist ? ( - - - - - + <> + + - - - - - {location.view_type === 'grid' && } - {location.view_type === 'list' && } - - + + + + + + + + + {location.view_type === 'grid' && } + {location.view_type === 'list' && } + + + ) : ( )} diff --git a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx index 862a4f1a56c12a..764b8fcd0444b1 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.test.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.test.tsx @@ -159,7 +159,7 @@ describe('Network page - rendering', () => { myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); wrapper.update(); expect(wrapper.find(NetworkRoutes).props().filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' ); }); }); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index c1f501d3f70945..2e41e291156aaa 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -44,6 +44,7 @@ import { APP_PATH, DEFAULT_INDEX_KEY, DETECTION_ENGINE_INDEX_URL, + DEFAULT_ALERTS_INDEX, } from '../common/constants'; import { SecurityPageName } from './app/types'; @@ -446,6 +447,9 @@ export class Plugin implements IPlugin { if (!this._store) { + const experimentalFeatures = parseExperimentalConfigValue( + this.config.enableExperimental || [] + ); const defaultIndicesName = coreStart.uiSettings.get(DEFAULT_INDEX_KEY); const [ { createStore, createInitialState }, @@ -474,9 +478,15 @@ export class Plugin implements IPlugin { isEventViewer, }) ).toEqual({ - filterQuery: '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', + filterQuery: '{"bool":{"must":[],"filter":[],"should":[],"must_not":[]}}', }); }); @@ -299,7 +299,7 @@ describe('Combined Queries', () => { }) ).toEqual({ filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}},{"exists":{"field":"host.name"}}],"should":[],"must_not":[]}}', + '{"bool":{"must":[],"filter":[{"exists":{"field":"host.name"}}],"should":[],"must_not":[]}}', }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts index 068f813e0fc40a..2f815cc44b3acb 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_ALERTS_INDEX } from '../../../common/constants'; import { TimelineId } from '../../../common/types/timeline'; export const detectionsTimelineIds = [ @@ -12,7 +13,14 @@ export const detectionsTimelineIds = [ TimelineId.detectionsRulesDetailsPage, ]; -export const skipQueryForDetectionsPage = (id: string, defaultIndex: string[]) => +// TODO: Once we are past experimental phase `useRuleRegistry` should be removed +export const skipQueryForDetectionsPage = ( + id: string, + defaultIndex: string[], + useRuleRegistry = false +) => id != null && detectionsTimelineIds.some((timelineId) => timelineId === id) && - !defaultIndex.some((di) => di.toLowerCase().startsWith('.siem-signals')); + !defaultIndex.some((di) => + di.toLowerCase().startsWith(useRuleRegistry ? DEFAULT_ALERTS_INDEX : '.siem-signals') + ); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx index 1032d0ec1672ac..62846eb01e60f6 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -9,6 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { initSortDefault, TimelineArgs, useTimelineEvents, UseTimelineEventsProps } from '.'; import { SecurityPageName } from '../../../common/constants'; import { TimelineId } from '../../../common/types/timeline'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { mockTimelineData } from '../../common/mock'; import { useRouteSpy } from '../../common/utils/route/use_route_spy'; @@ -26,6 +27,9 @@ const mockEvents = mockTimelineData.filter((i, index) => index <= 11); const mockSearch = jest.fn(); +jest.mock('../../common/hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; + jest.mock('../../common/lib/kibana', () => ({ useToasts: jest.fn().mockReturnValue({ addError: jest.fn(), @@ -93,6 +97,7 @@ mockUseRouteSpy.mockReturnValue([ ]); describe('useTimelineEvents', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); beforeEach(() => { mockSearch.mockReset(); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 92199336b978c1..17c107899d85ab 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -13,6 +13,7 @@ import { Subscription } from 'rxjs'; import { ESQuery } from '../../../common/typed_json'; import { isCompleteResponse, isErrorResponse } from '../../../../../../src/plugins/data/public'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { inputsModel, KueryFilterQueryKind } from '../../common/store'; import { useKibana } from '../../common/lib/kibana'; import { createFilter } from '../../common/containers/helpers'; @@ -197,6 +198,9 @@ export const useTimelineEvents = ({ }); const { addError, addWarning } = useAppToasts(); + // TODO: Once we are past experimental phase this code should be removed + const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled'); + const timelineSearch = useCallback( (request: TimelineRequest | null) => { if (request == null || pageName === '' || skip) { @@ -305,7 +309,10 @@ export const useTimelineEvents = ({ ); useEffect(() => { - if (skipQueryForDetectionsPage(id, indexNames) || indexNames.length === 0) { + if ( + skipQueryForDetectionsPage(id, indexNames, ruleRegistryEnabled) || + indexNames.length === 0 + ) { return; } @@ -364,7 +371,10 @@ export const useTimelineEvents = ({ activeTimeline.setActivePage(newActivePage); } } - if (!skipQueryForDetectionsPage(id, indexNames) && !deepEqual(prevRequest, currentRequest)) { + if ( + !skipQueryForDetectionsPage(id, indexNames, ruleRegistryEnabled) && + !deepEqual(prevRequest, currentRequest) + ) { return currentRequest; } return prevRequest; @@ -380,6 +390,7 @@ export const useTimelineEvents = ({ id, language, limit, + ruleRegistryEnabled, startDate, sort, fields, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts new file mode 100644 index 00000000000000..f7e0dd9eb36205 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { of } from 'rxjs'; +import { v4 } from 'uuid'; + +import { Logger } from 'kibana/server'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +import type { RuleDataClient } from '../../../../../../rule_registry/server'; +import { PluginSetupContract as AlertingPluginSetupContract } from '../../../../../../alerting/server'; +import { ConfigType } from '../../../../config'; + +export const createRuleTypeMocks = () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + let alertExecutor: (...args: any[]) => Promise; + + const mockedConfig$ = of({} as ConfigType); + + const loggerMock = ({ + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + } as unknown) as Logger; + + const alerting = { + registerType: ({ executor }) => { + alertExecutor = executor; + }, + } as AlertingPluginSetupContract; + + const scheduleActions = jest.fn(); + + const services = { + scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(), + alertInstanceFactory: jest.fn(() => ({ scheduleActions })), + findAlerts: jest.fn(), // TODO: does this stay? + alertWithPersistence: jest.fn(), + logger: loggerMock, + }; + + return { + dependencies: { + alerting, + config$: mockedConfig$, + logger: loggerMock, + ruleDataClient: ({ + getReader: () => { + return { + search: jest.fn(), + }; + }, + getWriter: () => { + return { + bulk: jest.fn(), + }; + }, + } as unknown) as RuleDataClient, + }, + services, + scheduleActions, + executor: async ({ params }: { params: Record }) => { + return alertExecutor({ + services, + params, + alertId: v4(), + startedAt: new Date(), + }); + }, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/threshold.ts new file mode 100644 index 00000000000000..40d2ed37a55769 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/threshold.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; + +export const mockThresholdResults = { + rawResponse: { + body: { + is_partial: false, + is_running: false, + took: 527, + timed_out: false, + hits: { + total: { + value: 0, + relation: 'eq', + }, + hits: [], + }, + aggregations: { + 'threshold_0:source.ip': { + buckets: [ + { + key: '127.0.0.1', + doc_count: 5, + 'threshold_1:host.name': { + buckets: [ + { + key: 'tardigrade', + doc_count: 3, + top_threshold_hits: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + hits: [ + { + ...sampleDocNoSortId(), + 'host.name': 'tardigrade', + }, + ], + }, + }, + cardinality_count: { + value: 3, + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts new file mode 100644 index 00000000000000..6529c594dd5a51 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; + +import { sequenceResponse } from '../../../search_strategy/timeline/eql/__mocks__'; + +import { createEqlAlertType } from './eql'; +import { createRuleTypeMocks } from './__mocks__/rule_type'; + +describe('EQL alerts', () => { + it('does not send an alert when sequence not found', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const eqlAlertType = createEqlAlertType(dependencies.ruleDataClient, dependencies.logger); + + dependencies.alerting.registerType(eqlAlertType); + + const params = { + eqlQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', + indexPatterns: ['*'], + }; + + services.scopedClusterClient.asCurrentUser.transport.request.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [], + sequences: [], + events: [], + total: { + relation: 'eq', + value: 0, + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).not.toBeCalled(); + }); + + it('sends a properly formatted alert when sequence is found', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const eqlAlertType = createEqlAlertType(dependencies.ruleDataClient, dependencies.logger); + + dependencies.alerting.registerType(eqlAlertType); + + const params = { + eqlQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]', + indexPatterns: ['*'], + }; + + services.scopedClusterClient.asCurrentUser.transport.request.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: sequenceResponse.rawResponse.body.hits, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).toBeCalled(); + /* + expect(services.alertWithPersistence).toBeCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + 'event.kind': 'signal', + 'kibana.rac.alert.building_block_type': 'default', + }), + ]) + ); + */ + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts new file mode 100644 index 00000000000000..39d02c808d09e5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import v4 from 'uuid/v4'; + +import { ApiResponse } from '@elastic/elasticsearch'; +import { schema } from '@kbn/config-schema'; +import { Logger } from '@kbn/logging'; + +import { + RuleDataClient, + createPersistenceRuleTypeFactory, +} from '../../../../../rule_registry/server'; +import { EQL_ALERT_TYPE_ID } from '../../../../common/constants'; +import { buildEqlSearchRequest } from '../../../../common/detection_engine/get_query_filter'; +import { BaseSignalHit, EqlSignalSearchResponse } from '../signals/types'; + +export const createEqlAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => { + const createPersistenceRuleType = createPersistenceRuleTypeFactory({ + ruleDataClient, + logger, + }); + return createPersistenceRuleType({ + id: EQL_ALERT_TYPE_ID, + name: 'EQL Rule', + validate: { + params: schema.object({ + eqlQuery: schema.string(), + indexPatterns: schema.arrayOf(schema.string()), + }), + }, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: { + context: [{ name: 'server', description: 'the server' }], + }, + minimumLicenseRequired: 'basic', + producer: 'security-solution', + async executor({ + startedAt, + services: { alertWithPersistence, findAlerts, scopedClusterClient }, + params: { indexPatterns, eqlQuery }, + }) { + const from = moment(startedAt).subtract(moment.duration(5, 'm')).toISOString(); // hardcoded 5-minute rule interval + const to = startedAt.toISOString(); + + const request = buildEqlSearchRequest( + eqlQuery, + indexPatterns, + from, + to, + 10, + undefined, + [], + undefined + ); + const { body: response } = (await scopedClusterClient.asCurrentUser.transport.request( + request + )) as ApiResponse; + + const buildSignalFromEvent = (event: BaseSignalHit) => { + return { + ...event, + 'event.kind': 'signal', + 'kibana.rac.alert.id': '???', + 'kibana.rac.alert.uuid': v4(), + '@timestamp': new Date().toISOString(), + }; + }; + + /* eslint-disable @typescript-eslint/no-explicit-any */ + let alerts: any[] = []; + if (response.hits.sequences !== undefined) { + alerts = response.hits.sequences.reduce((allAlerts: any[], sequence) => { + let previousAlertUuid: string | undefined; + return [ + ...allAlerts, + ...sequence.events.map((event, idx) => { + const alert = { + ...buildSignalFromEvent(event), + 'kibana.rac.alert.ancestors': previousAlertUuid != null ? [previousAlertUuid] : [], + 'kibana.rac.alert.building_block_type': 'default', + 'kibana.rac.alert.depth': idx, + }; + previousAlertUuid = alert['kibana.rac.alert.uuid']; + return alert; + }), + ]; + }, []); + } else if (response.hits.events !== undefined) { + alerts = response.hits.events.map((event) => { + return buildSignalFromEvent(event); + }, []); + } else { + throw new Error( + 'eql query response should have either `sequences` or `events` but had neither' + ); + } + + if (alerts.length > 0) { + alertWithPersistence(alerts).forEach((alert) => { + alert.scheduleActions('default', { server: 'server-test' }); + }); + } + + return { + lastChecked: new Date(), + }; + }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts new file mode 100644 index 00000000000000..c07d0436cc90d0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + +import { schema } from '@kbn/config-schema'; +import { KibanaRequest, Logger } from 'src/core/server'; +import { SavedObject } from 'src/core/types'; + +import { buildEsQuery, IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +import { createPersistenceRuleTypeFactory } from '../../../../../rule_registry/server'; +import { ML_ALERT_TYPE_ID } from '../../../../common/constants'; +import { SecurityRuleRegistry } from '../../../plugin'; + +const createSecurityMlRuleType = createPersistenceRuleTypeFactory(); + +import { + AlertInstanceContext, + AlertInstanceState, + AlertServices, +} from '../../../../../alerting/server'; +import { ListClient } from '../../../../../lists/server'; +import { isJobStarted } from '../../../../common/machine_learning/helpers'; +import { ExceptionListItemSchema } from '../../../../common/shared_imports'; +import { SetupPlugins } from '../../../plugin'; +import { RefreshTypes } from '../types'; +import { bulkCreateMlSignals } from '../signals/bulk_create_ml_signals'; +import { filterEventsAgainstList } from '../signals/filters/filter_events_against_list'; +import { findMlSignals } from '../signals/find_ml_signals'; +import { BuildRuleMessage } from '../signals/rule_messages'; +import { RuleStatusService } from '../signals/rule_status_service'; +import { MachineLearningRuleAttributes } from '../signals/types'; +import { createErrorsFromShard, createSearchAfterReturnType, mergeReturns } from '../signals/utils'; + +export const mlAlertType = createSecurityMlRuleType({ + id: ML_ALERT_TYPE_ID, + name: 'Machine Learning Rule', + validate: { + params: schema.object({ + indexPatterns: schema.arrayOf(schema.string()), + customQuery: schema.string(), + }), + }, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: { + context: [{ name: 'server', description: 'the server' }], + }, + minimumLicenseRequired: 'basic', + producer: 'security-solution', + async executor({ + services: { alertWithPersistence, findAlerts }, + params: { indexPatterns, customQuery }, + }) { + return { + lastChecked: new Date(), + }; + }, +}); +*/ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts new file mode 100644 index 00000000000000..e8c45e9ab70568 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 } from 'uuid'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; + +import { sampleDocNoSortId } from '../signals/__mocks__/es_results'; + +import { createQueryAlertType } from './query'; +import { createRuleTypeMocks } from './__mocks__/rule_type'; + +describe('Custom query alerts', () => { + it('does not send an alert when no events found', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const queryAlertType = createQueryAlertType(dependencies.ruleDataClient, dependencies.logger); + + dependencies.alerting.registerType(queryAlertType); + + const params = { + customQuery: 'dne:42', + indexPatterns: ['*'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [], + sequences: [], + events: [], + total: { + relation: 'eq', + value: 0, + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).not.toBeCalled(); + }); + + it('sends a properly formatted alert when events are found', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const queryAlertType = createQueryAlertType(dependencies.ruleDataClient, dependencies.logger); + + dependencies.alerting.registerType(queryAlertType); + + const params = { + customQuery: '*:*', + indexPatterns: ['*'], + }; + + services.scopedClusterClient.asCurrentUser.search.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [sampleDocNoSortId(v4()), sampleDocNoSortId(v4()), sampleDocNoSortId(v4())], + total: { + relation: 'eq', + value: 3, + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).toBeCalled(); + /* + expect(services.alertWithPersistence).toBeCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + 'event.kind': 'signal', + }), + ]) + ); + */ + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts new file mode 100644 index 00000000000000..3911dcabc34de8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryContainer } from '@elastic/elasticsearch/api/types'; +import { schema } from '@kbn/config-schema'; +import { Logger } from '@kbn/logging'; +import { ESSearchRequest } from 'typings/elasticsearch'; + +import { buildEsQuery, IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +import { + RuleDataClient, + createPersistenceRuleTypeFactory, +} from '../../../../../rule_registry/server'; +import { CUSTOM_ALERT_TYPE_ID } from '../../../../common/constants'; + +export const createQueryAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => { + const createPersistenceRuleType = createPersistenceRuleTypeFactory({ + ruleDataClient, + logger, + }); + return createPersistenceRuleType({ + id: CUSTOM_ALERT_TYPE_ID, + name: 'Custom Query Rule', + validate: { + params: schema.object({ + indexPatterns: schema.arrayOf(schema.string()), + customQuery: schema.string(), + }), + }, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: { + context: [{ name: 'server', description: 'the server' }], + }, + minimumLicenseRequired: 'basic', + producer: 'security-solution', + async executor({ + services: { alertWithPersistence, findAlerts }, + params: { indexPatterns, customQuery }, + }) { + try { + const indexPattern: IIndexPattern = { + fields: [], + title: indexPatterns.join(), + }; + + // TODO: kql or lucene? + + const esQuery = buildEsQuery( + indexPattern, + { query: customQuery, language: 'kuery' }, + [] + ) as QueryContainer; + const query: ESSearchRequest = { + body: { + query: esQuery, + fields: ['*'], + sort: { + '@timestamp': 'asc' as const, + }, + }, + }; + + const alerts = await findAlerts(query); + // console.log('alerts', alerts); + alertWithPersistence(alerts).forEach((alert) => { + alert.scheduleActions('default', { server: 'server-test' }); + }); + + return { + lastChecked: new Date(), + }; + } catch (error) { + logger.error(error); + } + }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh new file mode 100755 index 00000000000000..25e247a08ef46d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +curl -X POST http://localhost:5601/${BASE_PATH}/api/alerts/alert \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -H 'kbn-xsrf: true' \ + -H 'Content-Type: application/json' \ + --verbose \ + -d ' +{ + "params":{ + "indexPatterns": ["*"], + "eqlQuery": "sequence by host.name↵[any where true]↵[any where true]↵[any where true]" + }, + "consumer":"alerts", + "alertTypeId":"siem.eqlRule", + "schedule":{ + "interval":"1m" + }, + "actions":[], + "tags":[ + "eql", + "persistence" + ], + "notifyWhen":"onActionGroupChange", + "name":"Basic EQL rule" +}' + + diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh new file mode 100755 index 00000000000000..c34af7dee4044d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +curl -X POST http://localhost:5601/${BASE_PATH}/api/alerts/alert \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -H 'kbn-xsrf: true' \ + -H 'Content-Type: application/json' \ + --verbose \ + -d ' +{ + "params":{ + "indexPatterns": ["*"], + "customQuery": "*:*" + }, + "consumer":"alerts", + "alertTypeId":"siem.customRule", + "schedule":{ + "interval":"1m" + }, + "actions":[], + "tags":[ + "custom", + "persistence" + ], + "notifyWhen":"onActionGroupChange", + "name":"Basic custom query rule" +}' + + diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh new file mode 100755 index 00000000000000..8b486b165c34b3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. +# + +curl -X POST http://localhost:5601/${BASE_PATH}/api/alerts/alert \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -H 'kbn-xsrf: true' \ + -H 'Content-Type: application/json' \ + --verbose \ + -d ' +{ + "params":{ + "indexPatterns": ["*"], + "customQuery": "*:*", + "thresholdFields": ["source.ip", "destination.ip"], + "thresholdValue": 50, + "thresholdCardinality": [] + }, + "consumer":"alerts", + "alertTypeId":"siem.thresholdRule", + "schedule":{ + "interval":"1m" + }, + "actions":[], + "tags":[ + "persistence", + "threshold" + ], + "notifyWhen":"onActionGroupChange", + "name":"Basic Threshold rule" +}' + + diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts new file mode 100644 index 00000000000000..36e53b8154e70f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; + +import { createRuleTypeMocks } from './__mocks__/rule_type'; +import { mockThresholdResults } from './__mocks__/threshold'; +import { createThresholdAlertType } from './threshold'; + +describe('Threshold alerts', () => { + it('does not send an alert when threshold is not met', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const thresholdAlertType = createThresholdAlertType( + dependencies.ruleDataClient, + dependencies.logger + ); + + dependencies.alerting.registerType(thresholdAlertType); + + const params = { + indexPatterns: ['*'], + customQuery: '*:*', + thresholdFields: ['source.ip', 'host.name'], + thresholdValue: 4, + }; + + services.scopedClusterClient.asCurrentUser.search.mockReturnValue( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [], + sequences: [], + events: [], + total: { + relation: 'eq', + value: 0, + }, + }, + aggregations: { + 'threshold_0:source.ip': { + buckets: [], + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).not.toBeCalled(); + }); + + it('sends a properly formatted alert when threshold is met', async () => { + const { services, dependencies, executor } = createRuleTypeMocks(); + const thresholdAlertType = createThresholdAlertType( + dependencies.ruleDataClient, + dependencies.logger + ); + + dependencies.alerting.registerType(thresholdAlertType); + + const params = { + indexPatterns: ['*'], + customQuery: '*:*', + thresholdFields: ['source.ip', 'host.name'], + thresholdValue: 4, + }; + + services.scopedClusterClient.asCurrentUser.search + .mockReturnValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 0, + }, + }, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ) + .mockReturnValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise({ + hits: { + hits: [], + total: { + relation: 'eq', + value: 0, + }, + }, + aggregations: mockThresholdResults.rawResponse.body.aggregations, + took: 0, + timed_out: false, + _shards: { + failed: 0, + skipped: 0, + successful: 1, + total: 1, + }, + }) + ); + + await executor({ params }); + expect(services.alertInstanceFactory).toBeCalled(); + /* + expect(services.alertWithPersistence).toBeCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + 'event.kind': 'signal', + }), + ]) + ); + */ + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts new file mode 100644 index 00000000000000..d4721e8bab11dc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import v4 from 'uuid/v4'; + +import { schema } from '@kbn/config-schema'; +import { Logger } from '@kbn/logging'; + +import { AlertServices } from '../../../../../alerting/server'; +import { + RuleDataClient, + createPersistenceRuleTypeFactory, +} from '../../../../../rule_registry/server'; +import { THRESHOLD_ALERT_TYPE_ID } from '../../../../common/constants'; +import { SignalSearchResponse, ThresholdSignalHistory } from '../signals/types'; +import { + findThresholdSignals, + getThresholdBucketFilters, + getThresholdSignalHistory, + transformThresholdResultsToEcs, +} from '../signals/threshold'; +import { getFilter } from '../signals/get_filter'; +import { BuildRuleMessage } from '../signals/rule_messages'; + +interface RuleParams { + indexPatterns: string[]; + customQuery: string; + thresholdFields: string[]; + thresholdValue: number; + thresholdCardinality: Array<{ + field: string; + value: number; + }>; +} + +interface BulkCreateThresholdSignalParams { + results: SignalSearchResponse; + ruleParams: RuleParams; + services: AlertServices & { logger: Logger }; + inputIndexPattern: string[]; + ruleId: string; + startedAt: Date; + from: Date; + thresholdSignalHistory: ThresholdSignalHistory; + buildRuleMessage: BuildRuleMessage; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const formatThresholdSignals = (params: BulkCreateThresholdSignalParams): any[] => { + const thresholdResults = params.results; + const threshold = { + field: params.ruleParams.thresholdFields, + value: params.ruleParams.thresholdValue, + }; + const results = transformThresholdResultsToEcs( + thresholdResults, + params.ruleParams.indexPatterns.join(','), + params.startedAt, + params.from, + undefined, + params.services.logger, + threshold, + params.ruleId, + undefined, + params.thresholdSignalHistory + ); + return results.hits.hits.map((hit) => { + return { + ...hit, + 'event.kind': 'signal', + 'kibana.rac.alert.id': '???', + 'kibana.rac.alert.uuid': v4(), + '@timestamp': new Date().toISOString(), + }; + }); +}; + +export const createThresholdAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => { + const createPersistenceRuleType = createPersistenceRuleTypeFactory({ + ruleDataClient, + logger, + }); + return createPersistenceRuleType({ + id: THRESHOLD_ALERT_TYPE_ID, + name: 'Threshold Rule', + validate: { + params: schema.object({ + indexPatterns: schema.arrayOf(schema.string()), + customQuery: schema.string(), + thresholdFields: schema.arrayOf(schema.string()), + thresholdValue: schema.number(), + thresholdCardinality: schema.arrayOf( + schema.object({ + field: schema.string(), + value: schema.number(), + }) + ), + }), + }, + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + actionVariables: { + context: [{ name: 'server', description: 'the server' }], + }, + minimumLicenseRequired: 'basic', + producer: 'security-solution', + async executor({ startedAt, services, params, alertId }) { + const fromDate = moment(startedAt).subtract(moment.duration(5, 'm')); // hardcoded 5-minute rule interval + const from = fromDate.toISOString(); + const to = startedAt.toISOString(); + + // TODO: how to get the output index? + const outputIndex = ['.kibana-madi-8-alerts-security-solution-8.0.0-000001']; + const buildRuleMessage = (...messages: string[]) => messages.join(); + const timestampOverride = undefined; + + const { + thresholdSignalHistory, + searchErrors: previousSearchErrors, + } = await getThresholdSignalHistory({ + indexPattern: outputIndex, + from, + to, + services: (services as unknown) as AlertServices, + logger, + ruleId: alertId, + bucketByFields: params.thresholdFields, + timestampOverride, + buildRuleMessage, + }); + + const bucketFilters = await getThresholdBucketFilters({ + thresholdSignalHistory, + timestampOverride, + }); + + const esFilter = await getFilter({ + type: 'threshold', + filters: bucketFilters, + language: 'kuery', + query: params.customQuery, + savedId: undefined, + services: (services as unknown) as AlertServices, + index: params.indexPatterns, + lists: [], + }); + + const { + searchResult: thresholdResults, + searchErrors, + searchDuration: thresholdSearchDuration, + } = await findThresholdSignals({ + inputIndexPattern: params.indexPatterns, + from, + to, + services: (services as unknown) as AlertServices, + logger, + filter: esFilter, + threshold: { + field: params.thresholdFields, + value: params.thresholdValue, + cardinality: params.thresholdCardinality, + }, + timestampOverride, + buildRuleMessage, + }); + + logger.info(`Threshold search took ${thresholdSearchDuration}ms`); // TODO: rule status service + + const alerts = formatThresholdSignals({ + results: thresholdResults, + ruleParams: params, + services: (services as unknown) as AlertServices & { logger: Logger }, + inputIndexPattern: ['TODO'], + ruleId: alertId, + startedAt, + from: fromDate.toDate(), + thresholdSignalHistory, + buildRuleMessage, + }); + + const errors = searchErrors.concat(previousSearchErrors); + if (errors.length === 0) { + services.alertWithPersistence(alerts).forEach((alert) => { + alert.scheduleActions('default', { server: 'server-test' }); + }); + } else { + throw new Error(errors.join('\n')); + } + + return { + lastChecked: new Date(), + }; + }, + }); +}; 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 43377251019176..857762dec45e94 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 @@ -491,6 +491,72 @@ export const getFindResultStatus = (): SavedObjectsFindResponse => ({ + page: 1, + per_page: 6, + total: 2, + saved_objects: [], + aggregations: { + alertIds: { + buckets: [ + { + key: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + most_recent_statuses: { + hits: { + hits: [ + { + _source: { + 'siem-detection-engine-rule-status': { + alertId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + statusDate: '2020-02-18T15:26:49.783Z', + status: 'succeeded', + lastFailureAt: undefined, + lastSuccessAt: '2020-02-18T15:26:49.783Z', + lastFailureMessage: undefined, + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], + }, + }, + }, + ], + }, + }, + }, + { + key: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', + most_recent_statuses: { + hits: { + hits: [ + { + _source: { + 'siem-detection-engine-rule-status': { + alertId: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', + statusDate: '2020-02-18T15:15:58.806Z', + status: 'failed', + lastFailureAt: '2020-02-18T15:15:58.806Z', + lastSuccessAt: '2020-02-13T20:31:59.855Z', + lastFailureMessage: + 'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.', + lastSuccessMessage: 'succeeded', + lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), + gap: '500.32', + searchAfterTimeDurations: ['200.00'], + bulkCreateTimeDurations: ['800.43'], + }, + }, + }, + ], + }, + }, + }, + ], + }, + }, +}); + export const getEmptySignalsResponse = (): SignalSearchResponse => ({ took: 1, timed_out: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts index 6af4397a4193a6..3527e43c03d52b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/read_index_route.ts @@ -6,15 +6,17 @@ */ import { transformError, getIndexExists } from '@kbn/securitysolution-es-utils'; +import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; +import { ConfigType } from '../../../../config'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; +import { DEFAULT_ALERTS_INDEX, DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; import { SIGNALS_TEMPLATE_VERSION } from './get_signals_template'; import { getIndexVersion } from './get_index_version'; import { isOutdated } from '../../migrations/helpers'; -export const readIndexRoute = (router: SecuritySolutionPluginRouter) => { +export const readIndexRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { router.get( { path: DETECTION_ENGINE_INDEX_URL, @@ -34,8 +36,16 @@ export const readIndexRoute = (router: SecuritySolutionPluginRouter) => { return siemResponse.error({ statusCode: 404 }); } + // TODO: Once we are past experimental phase this code should be removed + const { ruleRegistryEnabled } = parseExperimentalConfigValue(config.enableExperimental); + if (ruleRegistryEnabled) { + return response.ok({ + body: { name: DEFAULT_ALERTS_INDEX, index_mapping_outdated: false }, + }); + } + const index = siemClient.getSignalsIndex(); - const indexExists = await getIndexExists(esClient, index); + const indexExists = ruleRegistryEnabled ? true : await getIndexExists(esClient, index); if (indexExists) { let mappingOutdated: boolean | null = null; 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 9b7e7bb42f4236..993d9300e414f4 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 @@ -6,6 +6,7 @@ */ import { transformError, getIndexExists } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -24,7 +25,8 @@ import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters' export const createRulesRoute = ( router: SecuritySolutionPluginRouter, - ml: SetupPlugins['ml'] + ml: SetupPlugins['ml'], + ruleDataClient?: RuleDataClient | null ): void => { router.post( { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts index 76fb9ac0c77e33..4b05f603b85b7c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { queryRulesSchema, @@ -22,7 +23,10 @@ import { deleteNotifications } from '../../notifications/delete_notifications'; import { deleteRuleActionsSavedObject } from '../../rule_actions/delete_rule_actions_saved_object'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; -export const deleteRulesRoute = (router: SecuritySolutionPluginRouter) => { +export const deleteRulesRoute = ( + router: SecuritySolutionPluginRouter, + ruleDataClient?: RuleDataClient | null +) => { router.delete( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 434ef0f88b1969..06f3ca83c4722a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -10,7 +10,7 @@ import { getAlertMock, getFindRequest, getFindResultWithSingleHit, - getFindResultStatus, + getFindBulkResultStatus, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { findRulesRoute } from './find_rules_route'; @@ -27,7 +27,7 @@ describe('find_rules', () => { clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); clients.alertsClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); - clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); + clients.savedObjectsClient.find.mockResolvedValue(getFindBulkResultStatus()); findRulesRoute(server.router); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts index ccf0a59e87c749..428978fe1d8207 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { findRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/find_rules_type_dependents'; import { findRulesSchema, @@ -15,13 +16,15 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { findRules } from '../../rules/find_rules'; import { buildSiemResponse } from '../utils'; - -import { getRuleActionsSavedObject } from '../../rule_actions/get_rule_actions_saved_object'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { transformFindAlerts } from './utils'; +import { getBulkRuleActionsSavedObject } from '../../rule_actions/get_bulk_rule_actions_saved_object'; -export const findRulesRoute = (router: SecuritySolutionPluginRouter) => { +export const findRulesRoute = ( + router: SecuritySolutionPluginRouter, + ruleDataClient?: RuleDataClient | null +) => { router.get( { path: `${DETECTION_ENGINE_RULES_URL}/_find`, @@ -60,44 +63,11 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter) => { filter: query.filter, fields: query.fields, }); - - // if any rules attempted to execute but failed before the rule executor is called, - // an execution status will be written directly onto the rule via the kibana alerting framework, - // which we are filtering on and will write a failure status - // for any rules found to be in a failing state into our rule status saved objects - const failingRules = rules.data.filter( - (rule) => rule.executionStatus != null && rule.executionStatus.status === 'error' - ); - - const ruleStatuses = await Promise.all( - rules.data.map(async (rule) => { - const results = await ruleStatusClient.find({ - perPage: 1, - sortField: 'statusDate', - sortOrder: 'desc', - search: rule.id, - searchFields: ['alertId'], - }); - const failingRule = failingRules.find((badRule) => badRule.id === rule.id); - if (failingRule != null) { - if (results.saved_objects.length > 0) { - results.saved_objects[0].attributes.status = 'failed'; - results.saved_objects[0].attributes.lastFailureAt = failingRule.executionStatus.lastExecutionDate.toISOString(); - } - } - return results; - }) - ); - const ruleActions = await Promise.all( - rules.data.map(async (rule) => { - const results = await getRuleActionsSavedObject({ - savedObjectsClient, - ruleAlertId: rule.id, - }); - - return results; - }) - ); + const alertIds = rules.data.map((rule) => rule.id); + const [ruleStatuses, ruleActions] = await Promise.all([ + ruleStatusClient.findBulk(alertIds, 1), + getBulkRuleActionsSavedObject({ alertIds, savedObjectsClient }), + ]); const transformed = transformFindAlerts(rules, ruleActions, ruleStatuses); if (transformed == null) { return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); 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 c3a53a1f393ec5..73f076649b72fe 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 @@ -7,9 +7,9 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { - getFindResultStatus, ruleStatusRequest, getAlertMock, + getFindBulkResultStatus, } from '../__mocks__/request_responses'; import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { findRulesStatusesRoute } from './find_rules_status_route'; @@ -26,7 +26,7 @@ describe('find_statuses', () => { beforeEach(async () => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - clients.savedObjectsClient.find.mockResolvedValue(getFindResultStatus()); // successful status search + clients.savedObjectsClient.find.mockResolvedValue(getFindBulkResultStatus()); // successful status search clients.alertsClient.get.mockResolvedValue(getAlertMock(getQueryRuleParams())); findRulesStatusesRoute(server.router); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index bd6e8fc9e7aadb..aed8b80e4f1332 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -9,14 +9,13 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { RuleStatusResponse } from '../../rules/types'; import { buildSiemResponse, mergeStatuses, getFailingRules } from '../utils'; - import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; import { findRulesStatusesSchema, FindRulesStatusesSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/find_rule_statuses_schema'; +import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; /** * Given a list of rule ids, return the current status and @@ -51,45 +50,27 @@ export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => const ids = body.ids; try { const ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient); - const failingRules = await getFailingRules(ids, alertsClient); + const [statusesById, failingRules] = await Promise.all([ + ruleStatusClient.findBulk(ids, 6), + getFailingRules(ids, alertsClient), + ]); - const statuses = await ids.reduce(async (acc, id) => { - const accumulated = await acc; - const lastFiveErrorsForId = await ruleStatusClient.find({ - perPage: 6, - sortField: 'statusDate', - sortOrder: 'desc', - search: id, - searchFields: ['alertId'], - }); + const statuses = ids.reduce((acc, id) => { + const lastFiveErrorsForId = statusesById[id]; - if (lastFiveErrorsForId.saved_objects.length === 0) { - return accumulated; + if (lastFiveErrorsForId == null || lastFiveErrorsForId.length === 0) { + return acc; } const failingRule = failingRules[id]; - const lastFailureAt = lastFiveErrorsForId.saved_objects[0].attributes.lastFailureAt; - - if ( - failingRule != null && - (lastFailureAt == null || - new Date(failingRule.executionStatus.lastExecutionDate) > new Date(lastFailureAt)) - ) { - const currentStatus = lastFiveErrorsForId.saved_objects[0]; - currentStatus.attributes.lastFailureMessage = `Reason: ${failingRule.executionStatus.error?.reason} Message: ${failingRule.executionStatus.error?.message}`; - currentStatus.attributes.lastFailureAt = failingRule.executionStatus.lastExecutionDate.toISOString(); - currentStatus.attributes.statusDate = failingRule.executionStatus.lastExecutionDate.toISOString(); - currentStatus.attributes.status = 'failed'; - const updatedLastFiveErrorsSO = [ - currentStatus, - ...lastFiveErrorsForId.saved_objects.slice(1), - ]; - return mergeStatuses(id, updatedLastFiveErrorsSO, accumulated); + if (failingRule != null) { + const currentStatus = mergeAlertWithSidecarStatus(failingRule, lastFiveErrorsForId[0]); + const updatedLastFiveErrorsSO = [currentStatus, ...lastFiveErrorsForId.slice(1)]; + return mergeStatuses(id, updatedLastFiveErrorsSO, acc); } - return mergeStatuses(id, [...lastFiveErrorsForId.saved_objects], accumulated); - }, Promise.resolve({})); - + return mergeStatuses(id, [...lastFiveErrorsForId], acc); + }, {}); return response.ok({ body: statuses }); } catch (err) { const error = transformError(err); 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 780c248183ab96..eaaa44fcf19160 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 @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -28,7 +29,11 @@ import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_s import { readRules } from '../../rules/read_rules'; import { PartialFilter } from '../../types'; -export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +export const patchRulesRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'], + ruleDataClient?: RuleDataClient | null +) => { router.patch( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts index ac45e5d2ed3b20..917da6c9708d54 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; import { queryRulesSchema, @@ -21,7 +22,10 @@ import { readRules } from '../../rules/read_rules'; import { getRuleActionsSavedObject } from '../../rule_actions/get_rule_actions_saved_object'; import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; -export const readRulesRoute = (router: SecuritySolutionPluginRouter) => { +export const readRulesRoute = ( + router: SecuritySolutionPluginRouter, + ruleDataClient?: RuleDataClient | null +) => { router.get( { path: DETECTION_ENGINE_RULES_URL, 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 aad0068758f7dc..0ff6cb3cd2d0f0 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 @@ -6,6 +6,7 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { RuleDataClient } from '../../../../../../rule_registry/server'; import { updateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; import type { SecuritySolutionPluginRouter } from '../../../../types'; @@ -22,7 +23,11 @@ import { updateRulesNotifications } from '../../rules/update_rules_notifications import { ruleStatusSavedObjectsClientFactory } from '../../signals/rule_status_saved_objects_client'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +export const updateRulesRoute = ( + router: SecuritySolutionPluginRouter, + ml: SetupPlugins['ml'], + ruleDataClient?: RuleDataClient | null +) => { router.put( { path: DETECTION_ENGINE_RULES_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index f2788ab1bd4c99..29e322d7fcab5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -27,7 +27,6 @@ import { PartialFilter } from '../../types'; import { BulkError, ImportSuccessError } from '../utils'; import { getOutputRuleAlertForRest } from '../__mocks__/utils'; import { PartialAlert } from '../../../../../../alerting/server'; -import { SanitizedAlert } from '../../../../../../alerting/server/types'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; @@ -256,7 +255,7 @@ describe('utils', () => { describe('transformFindAlerts', () => { test('outputs empty data set when data set is empty correct', () => { - const output = transformFindAlerts({ data: [], page: 1, perPage: 0, total: 0 }, []); + const output = transformFindAlerts({ data: [], page: 1, perPage: 0, total: 0 }, {}, {}); expect(output).toEqual({ data: [], page: 1, perPage: 0, total: 0 }); }); @@ -268,7 +267,8 @@ describe('utils', () => { total: 0, data: [getAlertMock(getQueryRuleParams())], }, - [] + {}, + {} ); const expected = getOutputRuleAlertForRest(); expect(output).toEqual({ @@ -278,20 +278,6 @@ describe('utils', () => { data: [expected], }); }); - - test('returns 500 if the data is not of type siem alert', () => { - const unsafeCast = ([{ name: 'something else' }] as unknown) as SanitizedAlert[]; - const output = transformFindAlerts( - { - data: unsafeCast, - page: 1, - perPage: 1, - total: 1, - }, - [] - ); - expect(output).toBeNull(); - }); }); describe('transform', () => { 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 466b8dd1842276..dc0cd2e497215f 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 @@ -6,7 +6,7 @@ */ import { countBy } from 'lodash/fp'; -import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject } from 'kibana/server'; import uuid from 'uuid'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; @@ -17,11 +17,10 @@ import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType, - isAlertTypes, IRuleSavedAttributesSavedObjectAttributes, isRuleStatusFindType, - isRuleStatusFindTypes, isRuleStatusSavedObjectType, + IRuleStatusSOAttributes, } from '../../rules/types'; import { createBulkErrorObject, @@ -34,6 +33,7 @@ import { import { RuleActions } from '../../rule_actions/types'; import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; import { RuleParams } from '../../schemas/rule_schemas'; +import { SanitizedAlert } from '../../../../../../alerting/common'; type PromiseFromStreams = ImportRulesSchemaDecoded | Error; @@ -103,11 +103,11 @@ export const transformTags = (tags: string[]): string[] => { // Transforms the data but will remove any null or undefined it encounters and not include // those on the export export const transformAlertToRule = ( - alert: RuleAlertType, + alert: SanitizedAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObject ): Partial => { - return internalRuleToAPIResponse(alert, ruleActions, ruleStatus); + return internalRuleToAPIResponse(alert, ruleActions, ruleStatus?.attributes); }; export const transformAlertsToRules = (alerts: RuleAlertType[]): Array> => { @@ -116,33 +116,24 @@ export const transformAlertsToRules = (alerts: RuleAlertType[]): Array, - ruleActions: Array, - ruleStatuses?: Array> + ruleActions: { [key: string]: RuleActions | undefined }, + ruleStatuses: { [key: string]: IRuleStatusSOAttributes[] | undefined } ): { page: number; perPage: number; total: number; data: Array>; } | null => { - if (!ruleStatuses && isAlertTypes(findResults.data)) { - return { - page: findResults.page, - perPage: findResults.perPage, - total: findResults.total, - data: findResults.data.map((alert, idx) => transformAlertToRule(alert, ruleActions[idx])), - }; - } else if (isAlertTypes(findResults.data) && isRuleStatusFindTypes(ruleStatuses)) { - return { - page: findResults.page, - perPage: findResults.perPage, - total: findResults.total, - data: findResults.data.map((alert, idx) => - transformAlertToRule(alert, ruleActions[idx], ruleStatuses[idx].saved_objects[0]) - ), - }; - } else { - return null; - } + return { + page: findResults.page, + perPage: findResults.perPage, + total: findResults.total, + data: findResults.data.map((alert) => { + const statuses = ruleStatuses[alert.id]; + const status = statuses ? statuses[0] : undefined; + return internalRuleToAPIResponse(alert, ruleActions[alert.id], status); + }), + }; }; export const transform = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts index 909c94f145528c..d6b998e3142349 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts @@ -14,7 +14,7 @@ import { getSignalsAggsAndQueryRequest, getEmptySignalsResponse, } from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; +import { requestContextMock, serverMock, requestMock, createMockConfig } from '../__mocks__'; import { querySignalsRoute } from './query_signals_route'; describe('query for signal', () => { @@ -27,7 +27,7 @@ describe('query for signal', () => { clients.clusterClient.callAsCurrentUser.mockResolvedValue(getEmptySignalsResponse()); - querySignalsRoute(server.router); + querySignalsRoute(server.router, createMockConfig()); }); describe('query and agg on signals index', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts index 91172a277bf54a..770c1a5da344f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -6,8 +6,13 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; +import { ConfigType } from '../../../../config'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; +import { + DEFAULT_ALERTS_INDEX, + DETECTION_ENGINE_QUERY_SIGNALS_URL, +} from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; @@ -16,7 +21,7 @@ import { QuerySignalsSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/query_signals_index_schema'; -export const querySignalsRoute = (router: SecuritySolutionPluginRouter) => { +export const querySignalsRoute = (router: SecuritySolutionPluginRouter, config: ConfigType) => { router.post( { path: DETECTION_ENGINE_QUERY_SIGNALS_URL, @@ -48,9 +53,12 @@ export const querySignalsRoute = (router: SecuritySolutionPluginRouter) => { const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.securitySolution!.getAppClient(); + // TODO: Once we are past experimental phase this code should be removed + const { ruleRegistryEnabled } = parseExperimentalConfigValue(config.enableExperimental); + try { const result = await clusterClient.callAsCurrentUser('search', { - index: siemClient.getSignalsIndex(), + index: ruleRegistryEnabled ? DEFAULT_ALERTS_INDEX : siemClient.getSignalsIndex(), body: { query, aggs, _source, track_total_hits, size }, ignoreUnavailable: true, }); 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 a09c4cd257618d..ce7d4b31733700 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 @@ -25,7 +25,7 @@ import { getFailingRules, } from './utils'; import { responseMock } from './__mocks__'; -import { exampleRuleStatus, exampleFindRuleStatusResponse } from '../signals/__mocks__/es_results'; +import { exampleRuleStatus } from '../signals/__mocks__/es_results'; import { getAlertMock } from './__mocks__/request_responses'; import { AlertExecutionStatusErrorReasons } from '../../../../../alerting/common'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; @@ -301,8 +301,8 @@ describe('utils', () => { const statusTwo = exampleRuleStatus(); statusTwo.attributes.status = 'failed'; const currentStatus = exampleRuleStatus(); - const foundRules = exampleFindRuleStatusResponse([currentStatus, statusOne, statusTwo]); - const res = mergeStatuses(currentStatus.attributes.alertId, foundRules.saved_objects, { + const foundRules = [currentStatus.attributes, statusOne.attributes, statusTwo.attributes]; + const res = mergeStatuses(currentStatus.attributes.alertId, foundRules, { 'myfakealertid-8cfac': { current_status: { alert_id: 'myfakealertid-8cfac', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 130084da21591b..9ff75726322a17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -14,11 +14,12 @@ import { RouteValidationFunction, KibanaResponseFactory, CustomHttpResponseOptions, - SavedObjectsFindResult, } from '../../../../../../../src/core/server'; import { AlertsClient } from '../../../../../alerting/server'; import { RuleStatusResponse, IRuleStatusSOAttributes } from '../rules/types'; +import { RuleParams } from '../schemas/rule_schemas'; + export interface OutputError { message: string; statusCode: number; @@ -277,7 +278,7 @@ export const convertToSnakeCase = >( */ export const mergeStatuses = ( id: string, - currentStatusAndFailures: Array>, + currentStatusAndFailures: IRuleStatusSOAttributes[], acc: RuleStatusResponse ): RuleStatusResponse => { if (currentStatusAndFailures.length === 0) { @@ -286,7 +287,7 @@ export const mergeStatuses = ( }; } const convertedCurrentStatus = convertToSnakeCase( - currentStatusAndFailures[0].attributes + currentStatusAndFailures[0] ); return { ...acc, @@ -294,12 +295,12 @@ export const mergeStatuses = ( current_status: convertedCurrentStatus, failures: currentStatusAndFailures .slice(1) - .map((errorItem) => convertToSnakeCase(errorItem.attributes)), + .map((errorItem) => convertToSnakeCase(errorItem)), }, } as RuleStatusResponse; }; -export type GetFailingRulesResult = Record; +export type GetFailingRulesResult = Record>; export const getFailingRules = async ( ids: string[], @@ -316,13 +317,11 @@ export const getFailingRules = async ( return errorRules .filter((rule) => rule.executionStatus.status === 'error') .reduce((acc, failingRule) => { - const accum = acc; - const theRule = failingRule; return { - [theRule.id]: { - ...theRule, + [failingRule.id]: { + ...failingRule, }, - ...accum, + ...acc, }; }, {}); } catch (exc) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/get_bulk_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/get_bulk_rule_actions_saved_object.ts new file mode 100644 index 00000000000000..1abb16ba4612c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/get_bulk_rule_actions_saved_object.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AlertServices } from '../../../../../alerting/server'; +import { ruleActionsSavedObjectType } from './saved_object_mappings'; +import { IRuleActionsAttributesSavedObjectAttributes } from './types'; +import { getRuleActionsFromSavedObject } from './utils'; +import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; +import { buildChunkedOrFilter } from '../signals/utils'; + +interface GetBulkRuleActionsSavedObject { + alertIds: string[]; + savedObjectsClient: AlertServices['savedObjectsClient']; +} + +export const getBulkRuleActionsSavedObject = async ({ + alertIds, + savedObjectsClient, +}: GetBulkRuleActionsSavedObject): Promise> => { + const filter = buildChunkedOrFilter( + `${ruleActionsSavedObjectType}.attributes.ruleAlertId`, + alertIds + ); + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + saved_objects, + } = await savedObjectsClient.find({ + type: ruleActionsSavedObjectType, + perPage: 10000, + filter, + }); + return saved_objects.reduce((acc: { [key: string]: RulesActionsSavedObject }, savedObject) => { + acc[savedObject.attributes.ruleAlertId] = getRuleActionsFromSavedObject(savedObject); + return acc; + }, {}); +}; 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 380eb085e0d5a8..601f3ebaa0f9e6 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 @@ -211,12 +211,6 @@ export const isRuleStatusFindType = ( return get('saved_objects', obj) != null; }; -export const isRuleStatusFindTypes = ( - obj: unknown[] | undefined -): obj is Array> => { - return obj ? obj.every((ruleStatus) => isRuleStatusFindType(ruleStatus)) : false; -}; - export interface CreateRulesOptions { alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index ee7ecaadfd95c0..a215da021d15ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -6,7 +6,6 @@ */ import uuid from 'uuid'; -import { SavedObject } from 'kibana/server'; import { normalizeMachineLearningJobIds, normalizeThresholdObject, @@ -29,8 +28,8 @@ import { AppClient } from '../../../types'; import { addTags } from '../rules/add_tags'; import { DEFAULT_MAX_SIGNALS, SERVER_APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { Alert } from '../../../../../alerting/common'; -import { IRuleSavedAttributesSavedObjectAttributes } from '../rules/types'; +import { SanitizedAlert } from '../../../../../alerting/common'; +import { IRuleStatusSOAttributes } from '../rules/types'; import { transformTags } from '../routes/rules/utils'; // These functions provide conversions from the request API schema to the internal rule schema and from the internal rule schema @@ -270,10 +269,11 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { }; export const internalRuleToAPIResponse = ( - rule: Alert, + rule: SanitizedAlert, ruleActions?: RuleActions | null, - ruleStatus?: SavedObject + ruleStatus?: IRuleStatusSOAttributes ): FullResponseSchema => { + const mergedStatus = ruleStatus ? mergeAlertWithSidecarStatus(rule, ruleStatus) : undefined; return { // Alerting framework params id: rule.id, @@ -293,11 +293,30 @@ export const internalRuleToAPIResponse = ( throttle: ruleActions?.ruleThrottle || 'no_actions', actions: ruleActions?.actions ?? [], // Rule status - status: ruleStatus?.attributes.status ?? undefined, - status_date: ruleStatus?.attributes.statusDate ?? undefined, - last_failure_at: ruleStatus?.attributes.lastFailureAt ?? undefined, - last_success_at: ruleStatus?.attributes.lastSuccessAt ?? undefined, - last_failure_message: ruleStatus?.attributes.lastFailureMessage ?? undefined, - last_success_message: ruleStatus?.attributes.lastSuccessMessage ?? undefined, + status: mergedStatus?.status ?? undefined, + status_date: mergedStatus?.statusDate ?? undefined, + last_failure_at: mergedStatus?.lastFailureAt ?? undefined, + last_success_at: mergedStatus?.lastSuccessAt ?? undefined, + last_failure_message: mergedStatus?.lastFailureMessage ?? undefined, + last_success_message: mergedStatus?.lastSuccessMessage ?? undefined, }; }; + +export const mergeAlertWithSidecarStatus = ( + alert: SanitizedAlert, + status: IRuleStatusSOAttributes +): IRuleStatusSOAttributes => { + if ( + new Date(alert.executionStatus.lastExecutionDate) > new Date(status.statusDate) && + alert.executionStatus.status === 'error' + ) { + return { + ...status, + lastFailureMessage: `Reason: ${alert.executionStatus.error?.reason} Message: ${alert.executionStatus.error?.message}`, + lastFailureAt: alert.executionStatus.lastExecutionDate.toISOString(), + statusDate: alert.executionStatus.lastExecutionDate.toISOString(), + status: 'failed', + }; + } + return status; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/rule_status_saved_objects_client.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/rule_status_saved_objects_client.mock.ts index b760cec9226db0..3dd328a9499386 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/rule_status_saved_objects_client.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/rule_status_saved_objects_client.mock.ts @@ -9,6 +9,7 @@ import { RuleStatusSavedObjectsClient } from '../rule_status_saved_objects_clien const createMockRuleStatusSavedObjectsClient = (): jest.Mocked => ({ find: jest.fn(), + findBulk: jest.fn(), create: jest.fn(), update: jest.fn(), delete: jest.fn(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_saved_objects_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_saved_objects_client.ts index ff1b0e27019edb..25d315279ad60f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_saved_objects_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_saved_objects_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { get } from 'lodash'; import { SavedObjectsClientContract, SavedObject, @@ -14,11 +15,13 @@ import { } from '../../../../../../../src/core/server'; import { ruleStatusSavedObjectType } from '../rules/saved_object_mappings'; import { IRuleStatusSOAttributes } from '../rules/types'; +import { buildChunkedOrFilter } from './utils'; export interface RuleStatusSavedObjectsClient { find: ( options?: Omit ) => Promise>; + findBulk: (ids: string[], statusesPerId: number) => Promise; create: (attributes: IRuleStatusSOAttributes) => Promise>; update: ( id: string, @@ -27,6 +30,10 @@ export interface RuleStatusSavedObjectsClient { delete: (id: string) => Promise<{}>; } +interface FindBulkResponse { + [key: string]: IRuleStatusSOAttributes[] | undefined; +} + export const ruleStatusSavedObjectsClientFactory = ( savedObjectsClient: SavedObjectsClientContract ): RuleStatusSavedObjectsClient => ({ @@ -35,6 +42,50 @@ export const ruleStatusSavedObjectsClientFactory = ( ...options, type: ruleStatusSavedObjectType, }), + findBulk: async (ids, statusesPerId) => { + if (ids.length === 0) { + return {}; + } + const filter = buildChunkedOrFilter(`${ruleStatusSavedObjectType}.attributes.alertId`, ids); + const order: 'desc' = 'desc'; + const aggs = { + alertIds: { + terms: { + field: `${ruleStatusSavedObjectType}.attributes.alertId`, + size: ids.length, + }, + aggs: { + most_recent_statuses: { + top_hits: { + sort: [ + { + [`${ruleStatusSavedObjectType}.statusDate`]: { + order, + }, + }, + ], + size: statusesPerId, + }, + }, + }, + }, + }; + const results = await savedObjectsClient.find({ + filter, + aggs, + type: ruleStatusSavedObjectType, + perPage: 0, + }); + const buckets = get(results, 'aggregations.alertIds.buckets'); + return buckets.reduce((acc: Record, bucket: unknown) => { + const key = get(bucket, 'key'); + const hits = get(bucket, 'most_recent_statuses.hits.hits'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const statuses = hits.map((hit: any) => hit._source['siem-detection-engine-rule-status']); + acc[key] = statuses; + return acc; + }, {}); + }, create: (attributes) => savedObjectsClient.create(ruleStatusSavedObjectType, attributes), update: (id, attributes) => savedObjectsClient.update(ruleStatusSavedObjectType, id, attributes), delete: (id) => savedObjectsClient.delete(ruleStatusSavedObjectType, id), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts index 986393d6d34546..ca7f22e4a75707 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts @@ -138,7 +138,7 @@ export const findThresholdSignals = async ({ logger, // @ts-expect-error refactor to pass type explicitly instead of unknown filter, - pageSize: 1, + pageSize: 0, sortOrder: 'desc', buildRuleMessage, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index b04eab1496e960..f49492939eeb10 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -39,6 +39,7 @@ import { createTotalHitsFromSearchResult, lastValidDate, calculateThresholdSignalUuid, + buildChunkedOrFilter, } from './utils'; import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { @@ -1473,4 +1474,26 @@ describe('utils', () => { expect(signalUuid).toEqual('ee8870dc-45ff-5e6c-a2f9-80886651ce03'); }); }); + + describe('buildChunkedOrFilter', () => { + test('should return undefined if no values are provided', () => { + const filter = buildChunkedOrFilter('field.name', []); + expect(filter).toEqual(undefined); + }); + + test('should return a filter with a single value', () => { + const filter = buildChunkedOrFilter('field.name', ['id-1']); + expect(filter).toEqual('field.name: ("id-1")'); + }); + + test('should return a filter with a multiple values', () => { + const filter = buildChunkedOrFilter('field.name', ['id-1', 'id-2']); + expect(filter).toEqual('field.name: ("id-1" OR "id-2")'); + }); + + test('should return a filter with a multiple values chunked', () => { + const filter = buildChunkedOrFilter('field.name', ['id-1', 'id-2', 'id-3'], 2); + expect(filter).toEqual('field.name: ("id-1" OR "id-2") OR field.name: ("id-3")'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 03a067af6066d4..cc4ed6a45807b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -10,7 +10,7 @@ import moment from 'moment'; import uuidv5 from 'uuid/v5'; import dateMath from '@elastic/datemath'; import type { estypes } from '@elastic/elasticsearch'; -import { isEmpty, partition } from 'lodash'; +import { chunk, isEmpty, partition } from 'lodash'; import { ApiResponse, Context } from '@elastic/elasticsearch/lib/Transport'; import { SortResults } from '@elastic/elasticsearch/api/types'; @@ -868,3 +868,16 @@ export const getSafeSortIds = (sortIds: SortResults | undefined) => { return sortId; }); }; + +export const buildChunkedOrFilter = (field: string, values: string[], chunkSize: number = 1024) => { + if (values.length === 0) { + return undefined; + } + const chunkedValues = chunk(values, chunkSize); + return chunkedValues + .map((subArray) => { + const joinedValues = subArray.map((value) => `"${value}"`).join(' OR '); + return `${field}: (${joinedValues})`; + }) + .join(' OR '); +}; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index aa37a0dc1f6270..2507475592e888 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { once } from 'lodash'; import { Observable } from 'rxjs'; import { i18n } from '@kbn/i18n'; import LRU from 'lru-cache'; @@ -27,8 +28,18 @@ import { PluginSetupContract as AlertingSetup, PluginStartContract as AlertPluginStartContract, } from '../../alerting/server'; + import { PluginStartContract as CasesPluginStartContract } from '../../cases/server'; +import { + ECS_COMPONENT_TEMPLATE_NAME, + TECHNICAL_COMPONENT_TEMPLATE_NAME, +} from '../../rule_registry/common/assets'; import { SecurityPluginSetup as SecuritySetup, SecurityPluginStart } from '../../security/server'; +import { + RuleDataClient, + RuleRegistryPluginSetupContract, + RuleRegistryPluginStartContract, +} from '../../rule_registry/server'; import { PluginSetupContract as FeaturesSetup } from '../../features/server'; import { MlPluginSetup as MlSetup } from '../../ml/server'; import { ListPluginSetup } from '../../lists/server'; @@ -38,6 +49,9 @@ import { ILicense, LicensingPluginStart } from '../../licensing/server'; import { FleetStartContract } from '../../fleet/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { compose } from './lib/compose/kibana'; +import { createQueryAlertType } from './lib/detection_engine/reference_rules/query'; +import { createEqlAlertType } from './lib/detection_engine/reference_rules/eql'; +import { createThresholdAlertType } from './lib/detection_engine/reference_rules/threshold'; import { initRoutes } from './routes'; import { isAlertExecutor } from './lib/detection_engine/signals/types'; import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule_alert_type'; @@ -54,6 +68,8 @@ import { SecurityPageName, SIGNALS_ID, NOTIFICATIONS_ID, + REFERENCE_RULE_ALERT_TYPE_ID, + REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID, } from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency'; @@ -87,6 +103,7 @@ export interface SetupPlugins { features: FeaturesSetup; lists?: ListPluginSetup; ml?: MlSetup; + ruleRegistry: RuleRegistryPluginSetupContract; security?: SecuritySetup; spaces?: SpacesSetup; taskManager?: TaskManagerSetupContract; @@ -99,6 +116,7 @@ export interface StartPlugins { data: DataPluginStart; fleet?: FleetStartContract; licensing: LicensingPluginStart; + ruleRegistry: RuleRegistryPluginStartContract; taskManager?: TaskManagerStartContract; telemetry?: TelemetryPluginStart; security: SecurityPluginStart; @@ -135,6 +153,7 @@ export class Plugin implements IPlugin, plugins: SetupPlugins) { this.logger.debug('plugin setup'); + this.setupPlugins = plugins; const config = this.config; const globalConfig = this.context.config.legacy.get(); @@ -195,13 +215,75 @@ export class Plugin implements IPlugin core.getStartServices().then(([coreStart]) => coreStart); + + const ready = once(async () => { + const componentTemplateName = ruleDataService.getFullAssetName( + 'security-solution-mappings' + ); + + if (!ruleDataService.isWriteEnabled()) { + return; + } + + await ruleDataService.createOrUpdateComponentTemplate({ + name: componentTemplateName, + body: { + template: { + settings: { + number_of_shards: 1, + }, + mappings: {}, // TODO: Add mappings here via `mappingFromFieldMap()` + }, + }, + }); + + await ruleDataService.createOrUpdateIndexTemplate({ + name: ruleDataService.getFullAssetName('security-solution-index-template'), + body: { + index_patterns: [ruleDataService.getFullAssetName('security-solution*')], + composed_of: [ + ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME), + ruleDataService.getFullAssetName(ECS_COMPONENT_TEMPLATE_NAME), + componentTemplateName, + ], + }, + }); + }); + + ready().catch((err) => { + this.logger!.error(err); + }); + + ruleDataClient = new RuleDataClient({ + alias: plugins.ruleRegistry.ruleDataService.getFullAssetName('security-solution'), + getClusterClient: async () => { + const coreStart = await start(); + return coreStart.elasticsearch.client.asInternalUser; + }, + ready, + }); + + // Register reference rule types via rule-registry + this.setupPlugins.alerting.registerType(createQueryAlertType(ruleDataClient, this.logger)); + this.setupPlugins.alerting.registerType(createEqlAlertType(ruleDataClient, this.logger)); + this.setupPlugins.alerting.registerType( + createThresholdAlertType(ruleDataClient, this.logger) + ); + } + // TO DO We need to get the endpoint routes inside of initRoutes initRoutes( router, config, plugins.encryptedSavedObjects?.canEncrypt === true, plugins.security, - plugins.ml + plugins.ml, + ruleDataClient ); registerEndpointRoutes(router, endpointContext); registerLimitedConcurrencyRoutes(core); @@ -210,6 +292,16 @@ export class Plugin implements IPlugin { // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules // All REST rule creation, deletion, updating, etc...... - createRulesRoute(router, ml); - readRulesRoute(router); - updateRulesRoute(router, ml); - patchRulesRoute(router, ml); - deleteRulesRoute(router); - findRulesRoute(router); + createRulesRoute(router, ml, ruleDataClient); + readRulesRoute(router, ruleDataClient); + updateRulesRoute(router, ml, ruleDataClient); + patchRulesRoute(router, ml, ruleDataClient); + deleteRulesRoute(router, ruleDataClient); + findRulesRoute(router, ruleDataClient); + + // TODO: pass ruleDataClient to all relevant routes addPrepackedRulesRoute(router, config, security); getPrepackagedRulesStatusRoute(router, config, security); @@ -102,7 +107,7 @@ export const initRoutes = ( // POST /api/detection_engine/signals/status // Example usage can be found in security_solution/server/lib/detection_engine/scripts/signals setSignalsStatusRoute(router); - querySignalsRoute(router); + querySignalsRoute(router, config); getSignalsMigrationStatusRoute(router); createSignalsMigrationRoute(router, security); finalizeSignalsMigrationRoute(router, security); @@ -111,7 +116,7 @@ export const initRoutes = ( // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces createIndexRoute(router); - readIndexRoute(router); + readIndexRoute(router, config); deleteIndexRoute(router); // Detection Engine tags routes that have the REST endpoints of /api/detection_engine/tags diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx index e43db6b86f8b90..f489fd0c16455b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx @@ -33,6 +33,7 @@ const mockDeps = { trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, hostIsolationEnabled: false, + ruleRegistryEnabled: false, }, service: {} as EndpointAppContextService, } as EndpointAppContext, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 72b6667476dc62..8473a663084f5f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -125,133 +125,6 @@ "advancedSettings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", "advancedSettings.searchBarAriaLabel": "高度な設定を検索", "advancedSettings.voiceAnnouncement.ariaLabel": "詳細設定結果情報", - "apmOss.tutorial.apmAgents.statusCheck.btnLabel": "エージェントステータスを確認", - "apmOss.tutorial.apmAgents.statusCheck.errorMessage": "エージェントからまだデータを受け取っていません", - "apmOss.tutorial.apmAgents.statusCheck.successMessage": "1 つまたは複数のエージェントからデータを受け取りました", - "apmOss.tutorial.apmAgents.statusCheck.text": "アプリケーションが実行されていてエージェントがデータを送信していることを確認してください。", - "apmOss.tutorial.apmAgents.statusCheck.title": "エージェントステータス", - "apmOss.tutorial.apmAgents.title": "APM エージェント", - "apmOss.tutorial.apmServer.callOut.message": "ご使用の APM Server を 7.0 以上に更新してあることを確認してください。 Kibana の管理セクションにある移行アシスタントで 6.x データを移行することもできます。", - "apmOss.tutorial.apmServer.callOut.title": "重要:7.0 以上に更新中", - "apmOss.tutorial.apmServer.statusCheck.btnLabel": "APM Server ステータスを確認", - "apmOss.tutorial.apmServer.statusCheck.errorMessage": "APM Server が検出されました。7.0 以上に更新され、動作中であることを確認してください。", - "apmOss.tutorial.apmServer.statusCheck.successMessage": "APM Server が正しくセットアップされました", - "apmOss.tutorial.apmServer.statusCheck.text": "APM エージェントの導入を開始する前に、APM Server が動作していることを確認してください。", - "apmOss.tutorial.apmServer.statusCheck.title": "APM Server ステータス", - "apmOss.tutorial.apmServer.title": "APM Server", - "apmOss.tutorial.djangoClient.configure.commands.addAgentComment": "インストールされたアプリにエージェントを追加します", - "apmOss.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment": "パフォーマンスメトリックを送信するには、追跡ミドルウェアを追加します。", - "apmOss.tutorial.djangoClient.configure.commands.allowedCharactersComment": "a-z、A-Z、0-9、-、_、スペース", - "apmOss.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト:{defaultApmServerUrl}) を設定します", - "apmOss.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment": "任意のサービス名を設定します。使用できる文字:", - "apmOss.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment": "サービス環境を設定します", - "apmOss.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Server でシークレットトークンが必要な場合に使います", - "apmOss.tutorial.djangoClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.djangoClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づいてプログラムで作成されます。", - "apmOss.tutorial.djangoClient.configure.title": "エージェントの構成", - "apmOss.tutorial.djangoClient.install.textPre": "Python 用の APM エージェントを依存関係としてインストールします。", - "apmOss.tutorial.djangoClient.install.title": "APM エージェントのインストール", - "apmOss.tutorial.dotNetClient.configureAgent.textPost": "エージェントに「IConfiguration」インスタンスが渡されていない場合、 (例:非 ASP.NET Core アプリケーションの場合) 、エージェントを環境変数で構成することもできます。\n 高度な用途に関しては [ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.dotNetClient.configureAgent.title": "appsettings.json ファイルの例:", - "apmOss.tutorial.dotNetClient.configureApplication.textPost": "「IConfiguration」インスタンスを渡すのは任意であり、これにより、エージェントはこの「IConfiguration」インスタンス (例:「appsettings.json」ファイル) から構成を読み込みます。", - "apmOss.tutorial.dotNetClient.configureApplication.textPre": "「Elastic.Apm.NetCoreAll」パッケージの ASP.NET Core の場合、「Startup.cs」ファイル内の「Configure」メソドの「UseElasticApm」メソドを呼び出します。", - "apmOss.tutorial.dotNetClient.configureApplication.title": "エージェントをアプリケーションに追加", - "apmOss.tutorial.dotNetClient.download.textPre": "[NuGet] ({allNuGetPackagesLink}) から .NET アプリケーションにエージェントパッケージを追加してください。用途の異なる複数の NuGet パッケージがあります。\n\nEntity Framework Core の ASP.NET Core アプリケーションの場合は、[Elastic.Apm.NetCoreAll] ({netCoreAllApmPackageLink}) パッケージをダウンロードしてください。このパッケージは、自動的にすべてのエージェントコンポーネントをアプリケーションに追加します。\n\n 依存性を最低限に抑えたい場合、ASP.NET Coreの監視のみに[Elastic.Apm.AspNetCore] ({aspNetCorePackageLink}) パッケージ、またはEntity Framework Coreの監視のみに[Elastic.Apm.EfCore] ({efCorePackageLink}) パッケージを使用することができます。\n\n 手動インストルメンテーションのみにパブリック Agent API を使用する場合は、[Elastic.Apm] ({elasticApmPackageLink}) パッケージを使用してください。", - "apmOss.tutorial.dotNetClient.download.title": "APM エージェントのダウンロード", - "apmOss.tutorial.downloadServer.title": "APM Server をダウンロードして展開します", - "apmOss.tutorial.downloadServerRpm": "32 ビットパッケージをお探しですか?[ダウンロードページ] ({downloadPageLink}) をご覧ください。", - "apmOss.tutorial.downloadServerTitle": "32 ビットパッケージをお探しですか?[ダウンロードページ] ({downloadPageLink}) をご覧ください。", - "apmOss.tutorial.editConfig.textPre": "Elastic Stack の X-Pack セキュアバージョンをご使用の場合、「apm-server.yml」構成ファイルで認証情報を指定する必要があります。", - "apmOss.tutorial.editConfig.title": "構成を編集する", - "apmOss.tutorial.flaskClient.configure.commands.allowedCharactersComment": "a-z、A-Z、0-9、-、_、スペース", - "apmOss.tutorial.flaskClient.configure.commands.configureElasticApmComment": "またはアプリケーションの設定で ELASTIC_APM を使用するよう構成します。", - "apmOss.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment": "環境変数を使用して初期化します", - "apmOss.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト:{defaultApmServerUrl}) を設定します", - "apmOss.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment": "任意のサービス名を設定します。使用できる文字:", - "apmOss.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment": "サービス環境を設定します", - "apmOss.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Server でシークレットトークンが必要な場合に使います", - "apmOss.tutorial.flaskClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.flaskClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づいてプログラムで作成されます。", - "apmOss.tutorial.flaskClient.configure.title": "エージェントの構成", - "apmOss.tutorial.flaskClient.install.textPre": "Python 用の APM エージェントを依存関係としてインストールします。", - "apmOss.tutorial.flaskClient.install.title": "APM エージェントのインストール", - "apmOss.tutorial.goClient.configure.commands.initializeUsingEnvironmentVariablesComment": "環境変数を使用して初期化します:", - "apmOss.tutorial.goClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト:{defaultApmServerUrl}) を設定します", - "apmOss.tutorial.goClient.configure.commands.setServiceEnvironment": "サービス環境を設定します", - "apmOss.tutorial.goClient.configure.commands.setServiceNameComment": "サービス名を設定します。使用できる文字は # a-z、A-Z、0-9、-、_、スペースです。", - "apmOss.tutorial.goClient.configure.commands.usedExecutableNameComment": "ELASTIC_APM_SERVICE_NAME が指定されていない場合、実行ファイルの名前が使用されます。", - "apmOss.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment": "APM Server でシークレットトークンが必要な場合に使います", - "apmOss.tutorial.goClient.configure.textPost": "高度な構成に関しては [ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.goClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは実行ファイル名または「ELASTIC_APM_SERVICE_NAME」環境変数に基づいてプログラムで作成されます。", - "apmOss.tutorial.goClient.configure.title": "エージェントの構成", - "apmOss.tutorial.goClient.install.textPre": "Go の APM エージェントパッケージをインストールします。", - "apmOss.tutorial.goClient.install.title": "APM エージェントのインストール", - "apmOss.tutorial.goClient.instrument.textPost": "Go のソースコードのインストルメンテーションの詳細ガイドは、[ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.goClient.instrument.textPre": "提供されたインストルメンテーションモジュールの 1 つ、またはトレーサー API を直接使用して、Go アプリケーションにインストルメンテーションを設定します。", - "apmOss.tutorial.goClient.instrument.title": "アプリケーションのインストルメンテーション", - "apmOss.tutorial.introduction": "アプリケーション内から詳細なパフォーマンスメトリックやエラーを収集します。", - "apmOss.tutorial.javaClient.download.textPre": "[Maven Central] ({mavenCentralLink}) からエージェントをダウンロードします。アプリケーションにエージェントを依存関係として「追加しない」でください。", - "apmOss.tutorial.javaClient.download.title": "APM エージェントのダウンロード", - "apmOss.tutorial.javaClient.startApplication.textPost": "構成オプションと高度な用途に関しては、[ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.javaClient.startApplication.textPre": "「-javaagent」フラグを追加し、システムプロパティを使用してエージェントを構成します。\n\n * 任意のサービス名を設定します (使用可能な文字は a-z、A-Z、0-9、-、_、スペースです) \n * カスタム APM Server URL (デフォルト:{customApmServerUrl}) を設定します\n * APM Server シークレットトークンを設定します\n * サービス環境を設定します\n * アプリケーションのベースパッケージを設定します", - "apmOss.tutorial.javaClient.startApplication.title": "javaagent フラグでアプリケーションを起動", - "apmOss.tutorial.jsClient.enableRealUserMonitoring.textPre": "デフォルトでは、APM Server を実行すると RUM サポートは無効になります。RUM サポートを有効にする手順については、[ドキュメンテーション] ({documentationLink}) をご覧ください。", - "apmOss.tutorial.jsClient.enableRealUserMonitoring.title": "APM Server のリアルユーザー監視サポートを有効にする", - "apmOss.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト:{defaultApmServerUrl}) を設定します", - "apmOss.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment": "任意のサービス名を設定します (使用可能な文字は a-z、A-Z、0-9、-、_、スペースです) ", - "apmOss.tutorial.jsClient.installDependency.commands.setServiceEnvironmentComment": "サービス環境を設定します", - "apmOss.tutorial.jsClient.installDependency.commands.setServiceVersionComment": "サービスバージョンを設定します (ソースマップ機能に必要) ", - "apmOss.tutorial.jsClient.installDependency.textPost": "React や Angular などのフレームワーク統合には、カスタム依存関係があります。詳細は [統合ドキュメント] ({docLink}) をご覧ください。", - "apmOss.tutorial.jsClient.installDependency.textPre": "「npm install @elastic/apm-rum --save」でエージェントをアプリケーションへの依存関係としてインストールできます。\n\nその後で以下のようにアプリケーションでエージェントを初期化して構成できます。", - "apmOss.tutorial.jsClient.installDependency.title": "エージェントを依存関係としてセットアップ", - "apmOss.tutorial.jsClient.scriptTags.textPre": "または、スクリプトタグを使用してエージェントのセットアップと構成ができます。` を追加