diff --git a/config/serverless.yml b/config/serverless.yml index 6fbe9496f705b..3324fa53727b4 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -48,6 +48,8 @@ xpack.index_management.enableIndexStats: false xpack.index_management.editableIndexSettings: limited # Disable Storage size column in the Data streams table from Index Management UI xpack.index_management.enableDataStreamsStorageColumn: false +# Disable _source field in the Mappings editor's advanced options form from Index Management UI +xpack.index_management.enableMappingsSourceFieldSection: false # Disable toggle for enabling data retention in DSL form from Index Management UI xpack.index_management.enableTogglingDataRetention: false @@ -138,7 +140,7 @@ xpack.actions.queued.max: 10000 # Disables ESQL in advanced settings (hides it from the UI) uiSettings: overrides: - discover:enableESQL: true + enableESQL: true # Task Manager xpack.task_manager.allow_reading_invalid_state: false diff --git a/docs/discover/try-esql.asciidoc b/docs/discover/try-esql.asciidoc index 6c827240c1832..c95733fd42e6b 100644 --- a/docs/discover/try-esql.asciidoc +++ b/docs/discover/try-esql.asciidoc @@ -11,7 +11,7 @@ In this tutorial we'll use the {kib} sample web logs in Discover and Lens to exp [[prerequisite]] === Prerequisite -To be able to select **Try {esql}** from the Data views menu the `discover:enableESQL` setting must be enabled from **Stack Management > Advanced Settings**. It is enabled by default. +To be able to select **Try {esql}** from the Data views menu the `enableESQL` setting must be enabled from **Stack Management > Advanced Settings**. It is enabled by default. [float] [[tutorial-try-esql]] diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 9e400f01c88dd..3501b440d13ba 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -209,6 +209,9 @@ The default selection in the time filter. The maximum height that a cell occupies in a table. Set to 0 to disable truncation. +[[enableESQL]]`enableESQL`:: +This setting enables ES|QL in Kibana. + [float] [[presentation-labs]] ==== Presentation Labs @@ -290,9 +293,6 @@ in the current data view is used. The columns that appear by default on the *Discover* page. The default is `_source`. -[[discover:enableESQL]]`discover:enableESQL`:: -experimental[] Allows ES|QL queries for search. - [[discover-max-doc-fields-displayed]]`discover:maxDocFieldsDisplayed`:: Specifies the maximum number of fields to show in the document column of the *Discover* table. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 9d5cedff34c84..a1f0a4ebed8a4 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -14,8 +14,11 @@ KBN_PATH_CONF=/home/kibana/config ./bin/kibana -- The default host and port settings configure {kib} to run on `localhost:5601`. To change this behavior and allow remote users to connect, you'll need to update your `kibana.yml` file. You can also enable SSL and set a -variety of other options. Finally, environment variables can be injected into -configuration using `${MY_ENV_VAR}` syntax. +variety of other options. + +Environment variables can be injected into configuration using `${MY_ENV_VAR}` syntax. By default, configuration validation +will fail if an environment variable used in the config file is not present when Kibana starts. This behavior can be changed by using a default value +for the environment variable, using the `${MY_ENV_VAR:defaultValue}` syntax. `console.ui.enabled`:: Toggling this causes the server to regenerate assets on the next startup, diff --git a/docs/user/alerting/images/rule-types-index-threshold-select.png b/docs/user/alerting/images/rule-types-index-threshold-select.png deleted file mode 100644 index 98866694e6e36..0000000000000 Binary files a/docs/user/alerting/images/rule-types-index-threshold-select.png and /dev/null differ diff --git a/docs/user/alerting/rule-types/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc index 99c0e6f965306..17e50ceb40b66 100644 --- a/docs/user/alerting/rule-types/es-query.asciidoc +++ b/docs/user/alerting/rule-types/es-query.asciidoc @@ -12,7 +12,8 @@ The {es} query rule type runs a user-configured query, compares the number of matches to a configured threshold, and schedules actions to run when the threshold condition is met. -In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*, fill in the name and optional tags, then select *{es} query*. +In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*. +Select the *{es} query* rule type then fill in the name and optional tags. An {es} query rule can be defined using {es} Query Domain Specific Language (DSL), {es} Query Language (ES|QL), {kib} Query Language (KQL), or Lucene. [float] diff --git a/docs/user/alerting/rule-types/geo-rule-types.asciidoc b/docs/user/alerting/rule-types/geo-rule-types.asciidoc index af26780a3a6aa..96851919e4b58 100644 --- a/docs/user/alerting/rule-types/geo-rule-types.asciidoc +++ b/docs/user/alerting/rule-types/geo-rule-types.asciidoc @@ -10,7 +10,8 @@ The tracking containment rule alerts when an entity is contained or no longer contained within a boundary. -In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*, fill in the name and optional tags, then select *Tracking containment*. +In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*. +Select the *Tracking containment* rule type then fill in the name and optional tags. [float] === Define the conditions diff --git a/docs/user/alerting/rule-types/index-threshold.asciidoc b/docs/user/alerting/rule-types/index-threshold.asciidoc index a5f7c79e1be74..2e40c6c3bbcda 100644 --- a/docs/user/alerting/rule-types/index-threshold.asciidoc +++ b/docs/user/alerting/rule-types/index-threshold.asciidoc @@ -10,7 +10,8 @@ The index threshold rule type runs an {es} query. It aggregates field values from documents, compares them to threshold values, and schedules actions to run when the thresholds are met. -In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*, fill in the name and optional tags, then select *Index threshold*. +In *{stack-manage-app}* > *{rules-ui}*, click *Create rule*. +Select the *Index threshold* rule type then fill in the name and optional tags. [float] === Define the conditions @@ -107,15 +108,11 @@ You can also specify <>. In this example, you will use the {kib} <> to set up and tune the conditions on an index threshold rule. For this example, you want to detect when any of the top four sites serve more than 420,000 bytes over a 24 hour period. -. Open the main menu, then click *{stack-manage-app} > {rules-ui}*. +. Go to *{stack-manage-app} > {rules-ui}* and click *Create rule*. -. Create a new rule. +. Select the **Index threshold** rule type. -.. Provide a rule name and select the **Index threshold** rule type. -+ -[role="screenshot"] -image::user/alerting/images/rule-types-index-threshold-select.png[Choosing an index threshold rule type] -// NOTE: This is an autogenerated screenshot. Do not edit it directly. +.. Provide a rule name. .. Select an index. Click *Index*, and set *Indices to query* to `kibana_sample_data_logs`. Set the *Time field* to `@timestamp`. + diff --git a/examples/response_stream/server/plugin.ts b/examples/response_stream/server/plugin.ts index 9620a58ae517b..6bb0c55003059 100644 --- a/examples/response_stream/server/plugin.ts +++ b/examples/response_stream/server/plugin.ts @@ -27,7 +27,7 @@ export class ResponseStreamPlugin implements Plugin { public setup(core: CoreSetup, plugins: ResponseStreamSetupPlugins) { const router = core.http.createRouter(); - core.getStartServices().then(([_, depsStart]) => { + void core.getStartServices().then(([_, depsStart]) => { defineReducerStreamRoute(router, this.logger); defineSimpleStringStreamRoute(router, this.logger); }); diff --git a/examples/response_stream/server/routes/reducer_stream.ts b/examples/response_stream/server/routes/reducer_stream.ts index 5e03cd0732e74..02606b8c44756 100644 --- a/examples/response_stream/server/routes/reducer_stream.ts +++ b/examples/response_stream/server/routes/reducer_stream.ts @@ -85,44 +85,45 @@ export const defineReducerStreamRoute = (router: IRouter, logger: Logger) => { let progress = 0; async function pushStreamUpdate() { - setTimeout(() => { - try { - progress++; - - if (progress > 100 || shouldStop) { - end(); - return; - } - - push(updateProgressAction(progress)); - - const randomEntity = entities[Math.floor(Math.random() * entities.length)]; - const randomAction = actions[Math.floor(Math.random() * actions.length)]; - - if (randomAction === 'add') { - const randomCommits = Math.floor(Math.random() * 100); - push(addToEntityAction(randomEntity, randomCommits)); - } else if (randomAction === 'delete') { - push(deleteEntityAction(randomEntity)); - } else if (randomAction === 'throw-error') { - // Throw an error. It should not crash Kibana! - // It should be caught and logged to the Kibana server console. - throw new Error('There was a (simulated) server side error!'); - } else if (randomAction === 'emit-error') { - // Emit an error as a stream action. - push(errorAction('(Simulated) error pushed to the stream')); - return; - } - - pushStreamUpdate(); - } catch (e) { - logger.error(e); + await new Promise((resolve) => + setTimeout(resolve, Math.floor(Math.random() * maxTimeoutMs)) + ); + try { + progress++; + + if (progress > 100 || shouldStop) { + end(); + return; } - }, Math.floor(Math.random() * maxTimeoutMs)); + + push(updateProgressAction(progress)); + + const randomEntity = entities[Math.floor(Math.random() * entities.length)]; + const randomAction = actions[Math.floor(Math.random() * actions.length)]; + + if (randomAction === 'add') { + const randomCommits = Math.floor(Math.random() * 100); + push(addToEntityAction(randomEntity, randomCommits)); + } else if (randomAction === 'delete') { + push(deleteEntityAction(randomEntity)); + } else if (randomAction === 'throw-error') { + // Throw an error. It should not crash Kibana! + // It should be caught and logged to the Kibana server console. + throw new Error('There was a (simulated) server side error!'); + } else if (randomAction === 'emit-error') { + // Emit an error as a stream action. + push(errorAction('(Simulated) error pushed to the stream')); + return; + } + + void pushStreamUpdate(); + } catch (e) { + logger.error(e); + } } // do not call this using `await` so it will run asynchronously while we return the stream already. - pushStreamUpdate(); + void pushStreamUpdate(); return response.ok(responseWithHeaders); } diff --git a/examples/response_stream/server/routes/single_string_stream.ts b/examples/response_stream/server/routes/single_string_stream.ts index 26c26f0fc6b66..d9cb65686b71e 100644 --- a/examples/response_stream/server/routes/single_string_stream.ts +++ b/examples/response_stream/server/routes/single_string_stream.ts @@ -67,7 +67,7 @@ export const defineSimpleStringStreamRoute = (router: IRouter, logger: Logger) = await timeout(Math.floor(Math.random() * maxTimeoutMs)); if (!shouldStop) { - pushStreamUpdate(); + void pushStreamUpdate(); } } else { end(); @@ -78,7 +78,7 @@ export const defineSimpleStringStreamRoute = (router: IRouter, logger: Logger) = } // do not call this using `await` so it will run asynchronously while we return the stream already. - pushStreamUpdate(); + void pushStreamUpdate(); return response.ok(responseWithHeaders); } diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts index 57a8b055f9b44..27680a3287aba 100644 --- a/examples/search_examples/server/plugin.ts +++ b/examples/search_examples/server/plugin.ts @@ -48,7 +48,7 @@ export class SearchExamplesPlugin this.logger.debug('search_examples: Setup'); const router = core.http.createRouter(); - core.getStartServices().then(([_, depsStart]) => { + void core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); const fibonacciStrategy = fibonacciStrategyProvider(); deps.data.search.registerSearchStrategy('myStrategy', myStrategy); diff --git a/package.json b/package.json index 1e19c09f25ead..27c28c9ff5699 100644 --- a/package.json +++ b/package.json @@ -990,7 +990,7 @@ "deepmerge": "^4.2.2", "del": "^6.1.0", "diff": "^5.1.0", - "elastic-apm-node": "^4.5.2", + "elastic-apm-node": "^4.5.3", "email-addresses": "^5.0.0", "eventsource-parser": "^1.1.1", "execa": "^5.1.1", @@ -1053,7 +1053,7 @@ "lz-string": "^1.4.4", "mapbox-gl-draw-rectangle-mode": "1.0.4", "maplibre-gl": "3.1.0", - "markdown-it": "^12.3.2", + "markdown-it": "^14.1.0", "mdast-util-to-hast": "10.2.0", "memoize-one": "^6.0.0", "mime": "^2.4.4", diff --git a/packages/core/http/core-http-server/src/versioning/types.ts b/packages/core/http/core-http-server/src/versioning/types.ts index baa550b253544..b7aed68438fdb 100644 --- a/packages/core/http/core-http-server/src/versioning/types.ts +++ b/packages/core/http/core-http-server/src/versioning/types.ts @@ -32,7 +32,7 @@ export type VersionedRouteConfig = Omit< RouteConfig, 'validate' | 'options' > & { - options?: Omit, 'access'>; + options?: Omit, 'access' | 'description'>; /** See {@link RouteConfigOptions['access']} */ access: Exclude['access'], undefined>; /** diff --git a/packages/kbn-config/src/__fixtures__/en_var_with_defaults.yml b/packages/kbn-config/src/__fixtures__/en_var_with_defaults.yml new file mode 100644 index 0000000000000..250f863ff4ff6 --- /dev/null +++ b/packages/kbn-config/src/__fixtures__/en_var_with_defaults.yml @@ -0,0 +1,4 @@ +foo: 'pre-${KBN_ENV_VAR1}-mid-${KBN_ENV_VAR2:default2}-post' +nested_list: + - id: 'a' + values: ['${KBN_ENV_VAR1:default1}', '${KBN_ENV_VAR2:default2}'] diff --git a/packages/kbn-config/src/raw/read_config.test.ts b/packages/kbn-config/src/raw/read_config.test.ts index 750d10940e5ce..4a3754def8ae7 100644 --- a/packages/kbn-config/src/raw/read_config.test.ts +++ b/packages/kbn-config/src/raw/read_config.test.ts @@ -130,3 +130,26 @@ test('supports unsplittable key syntax on nested list', () => { } `); }); + +test('supports var:default syntax', () => { + process.env.KBN_ENV_VAR1 = 'val1'; + + const config = getConfigFromFiles([fixtureFile('/en_var_with_defaults.yml')]); + + delete process.env.KBN_ENV_VAR1; + + expect(config).toMatchInlineSnapshot(` + Object { + "foo": "pre-val1-mid-default2-post", + "nested_list": Array [ + Object { + "id": "a", + "values": Array [ + "val1", + "default2", + ], + }, + ], + } + `); +}); diff --git a/packages/kbn-config/src/raw/read_config.ts b/packages/kbn-config/src/raw/read_config.ts index 52ffcaa1bd190..1126ac1bc3f81 100644 --- a/packages/kbn-config/src/raw/read_config.ts +++ b/packages/kbn-config/src/raw/read_config.ts @@ -11,21 +11,10 @@ import { safeLoad } from 'js-yaml'; import { set } from '@kbn/safer-lodash-set'; import { isPlainObject } from 'lodash'; import { ensureValidObjectPath } from '@kbn/std'; -import { splitKey, getUnsplittableKey } from './utils'; +import { splitKey, getUnsplittableKey, replaceEnvVarRefs } from './utils'; const readYaml = (path: string) => safeLoad(readFileSync(path, 'utf8')); -function replaceEnvVarRefs(val: string) { - return val.replace(/\$\{(\w+)\}/g, (match, envVarName) => { - const envVarValue = process.env[envVarName]; - if (envVarValue !== undefined) { - return envVarValue; - } - - throw new Error(`Unknown environment variable referenced in config : ${envVarName}`); - }); -} - interface YamlEntry { path: string[]; value: any; diff --git a/packages/kbn-config/src/raw/utils.test.ts b/packages/kbn-config/src/raw/utils.test.ts index 26ecba09d9a98..0dc96ef4593d0 100644 --- a/packages/kbn-config/src/raw/utils.test.ts +++ b/packages/kbn-config/src/raw/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { splitKey, getUnsplittableKey } from './utils'; +import { splitKey, getUnsplittableKey, replaceEnvVarRefs } from './utils'; describe('splitKey', () => { it('correctly splits on the dot delimiter', () => { @@ -33,3 +33,39 @@ describe('getUnsplittableKey', () => { expect(getUnsplittableKey('foo.bar')).toEqual(undefined); }); }); + +describe('replaceEnvVarRefs', () => { + it('throws an error if the variable is not defined', () => { + expect(() => replaceEnvVarRefs('${VAR_1}', {})).toThrowErrorMatchingInlineSnapshot( + `"Unknown environment variable referenced in config : VAR_1"` + ); + }); + it('replaces the environment variable with its value', () => { + expect(replaceEnvVarRefs('${VAR_1}', { VAR_1: 'foo' })).toEqual('foo'); + }); + it('replaces the environment variable within a longer string', () => { + expect(replaceEnvVarRefs('hello ${VAR_1} bar', { VAR_1: 'foo' })).toEqual('hello foo bar'); + }); + it('replaces multiple occurrences of the same variable', () => { + expect(replaceEnvVarRefs('${VAR_1}-${VAR_1}', { VAR_1: 'foo' })).toEqual('foo-foo'); + }); + it('replaces multiple occurrences of different variables', () => { + expect(replaceEnvVarRefs('${VAR_1}-${VAR_2}', { VAR_1: 'foo', VAR_2: 'bar' })).toEqual( + 'foo-bar' + ); + }); + it('uses the default value if specified and the var is not defined', () => { + expect(replaceEnvVarRefs('${VAR:default}', {})).toEqual('default'); + }); + it('uses the value from the var if specified even with a default value', () => { + expect(replaceEnvVarRefs('${VAR:default}', { VAR: 'value' })).toEqual('value'); + }); + it('supports defining a default value for multiple variables', () => { + expect(replaceEnvVarRefs('${VAR1:var}:${VAR2:var2}', {})).toEqual('var:var2'); + }); + it('only use default value for variables that are not set', () => { + expect(replaceEnvVarRefs('${VAR1:default1}:${VAR2:default2}', { VAR2: 'var2' })).toEqual( + 'default1:var2' + ); + }); +}); diff --git a/packages/kbn-config/src/raw/utils.ts b/packages/kbn-config/src/raw/utils.ts index b44a3c14477aa..6fbcd767bafc3 100644 --- a/packages/kbn-config/src/raw/utils.ts +++ b/packages/kbn-config/src/raw/utils.ts @@ -19,3 +19,25 @@ export const getUnsplittableKey = (rawKey: string): string | undefined => { } return undefined; }; + +export function replaceEnvVarRefs( + val: string, + env: { + [key: string]: string | undefined; + } = process.env +) { + return val.replace(/\$\{(\w+)(:(\w+))?\}/g, (match, ...groups) => { + const envVarName = groups[0]; + const defaultValue = groups[2]; + + const envVarValue = env[envVarName]; + if (envVarValue !== undefined) { + return envVarValue; + } + if (defaultValue !== undefined) { + return defaultValue; + } + + throw new Error(`Unknown environment variable referenced in config : ${envVarName}`); + }); +} diff --git a/packages/kbn-discover-utils/index.ts b/packages/kbn-discover-utils/index.ts index 0434a05a9ce92..7a7fedfb5f1e3 100644 --- a/packages/kbn-discover-utils/index.ts +++ b/packages/kbn-discover-utils/index.ts @@ -13,7 +13,6 @@ export { DEFAULT_COLUMNS_SETTING, DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY, - ENABLE_ESQL, FIELDS_LIMIT_SETTING, HIDE_ANNOUNCEMENTS, MAX_DOC_FIELDS_DISPLAYED, diff --git a/packages/kbn-discover-utils/src/constants.ts b/packages/kbn-discover-utils/src/constants.ts index 7582b610c6638..acd34290d29de 100644 --- a/packages/kbn-discover-utils/src/constants.ts +++ b/packages/kbn-discover-utils/src/constants.ts @@ -12,7 +12,6 @@ export const CONTEXT_TIE_BREAKER_FIELDS_SETTING = 'context:tieBreakerFields'; export const DEFAULT_COLUMNS_SETTING = 'defaultColumns'; export const DOC_HIDE_TIME_COLUMN_SETTING = 'doc_table:hideTimeColumn'; export const DOC_TABLE_LEGACY = 'doc_table:legacy'; -export const ENABLE_ESQL = 'discover:enableESQL'; export const FIELDS_LIMIT_SETTING = 'fields:popularLimit'; export const HIDE_ANNOUNCEMENTS = 'hideAnnouncements'; export const MAX_DOC_FIELDS_DISPLAYED = 'discover:maxDocFieldsDisplayed'; diff --git a/packages/kbn-esql-utils/constants.ts b/packages/kbn-esql-utils/constants.ts index 51dcbab83654b..a50f03ebea1e2 100644 --- a/packages/kbn-esql-utils/constants.ts +++ b/packages/kbn-esql-utils/constants.ts @@ -9,3 +9,4 @@ // we are expecting to retrieve this from an API instead // https://github.com/elastic/elasticsearch/issues/107069 export const ESQL_LATEST_VERSION = '2024.04.01'; +export const ENABLE_ESQL = 'enableESQL'; diff --git a/packages/kbn-esql-utils/index.ts b/packages/kbn-esql-utils/index.ts index 0d04f40b85612..f8e93981d9c24 100644 --- a/packages/kbn-esql-utils/index.ts +++ b/packages/kbn-esql-utils/index.ts @@ -19,4 +19,4 @@ export { TextBasedLanguages, } from './src'; -export { ESQL_LATEST_VERSION } from './constants'; +export { ESQL_LATEST_VERSION, ENABLE_ESQL } from './constants'; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index 809552f02b999..4be855868d39b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -559,7 +559,10 @@ describe('autocomplete', () => { } describe('sort', () => { - testSuggestions('from a | sort ', getFieldNamesByType('any')); + testSuggestions('from a | sort ', [ + ...getFieldNamesByType('any'), + ...getFunctionSignaturesByReturnType('sort', 'any', { evalMath: true }), + ]); testSuggestions('from a | sort stringField ', ['asc', 'desc', ',', '|']); testSuggestions('from a | sort stringField desc ', ['nulls first', 'nulls last', ',', '|']); // @TODO: improve here diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index 6c25afe41f848..71494b64cf790 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -594,7 +594,11 @@ async function getExpressionSuggestionsByType( option?.name, getFieldsByType, { - functions: canHaveAssignments, + // TODO instead of relying on canHaveAssignments and other command name checks + // we should have a more generic way to determine if a command can have functions. + // I think it comes down to the definition of 'column' since 'any' should always + // include functions. + functions: canHaveAssignments || command.name === 'sort', fields: !argDef.constantOnly, variables: anyVariables, literals: argDef.constantOnly, diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts index 805bec79b03e4..20adda63602df 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts @@ -19,7 +19,7 @@ function createMathDefinition( type: 'builtin', name, description, - supportedCommands: ['eval', 'where', 'row', 'stats'], + supportedCommands: ['eval', 'where', 'row', 'stats', 'sort'], supportedOptions: ['by'], signatures: types.map((type) => { if (Array.isArray(type)) { @@ -59,7 +59,7 @@ function createComparisonDefinition( type: 'builtin' as const, name, description, - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate, signatures: [ @@ -296,7 +296,7 @@ export const builtinFunctions: FunctionDefinition[] = [ ignoreAsSuggestion: /not/.test(name), name, description, - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], supportedOptions: ['by'], signatures: [ { @@ -322,7 +322,7 @@ export const builtinFunctions: FunctionDefinition[] = [ ignoreAsSuggestion: /not/.test(name), name, description, - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], signatures: [ { params: [ @@ -371,7 +371,7 @@ export const builtinFunctions: FunctionDefinition[] = [ type: 'builtin' as const, name, description, - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], supportedOptions: ['by'], signatures: [ { @@ -410,7 +410,7 @@ export const builtinFunctions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.notDoc', { defaultMessage: 'Not', }), - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], supportedOptions: ['by'], signatures: [ { @@ -436,7 +436,7 @@ export const builtinFunctions: FunctionDefinition[] = [ type: 'builtin', name, description, - supportedCommands: ['eval', 'where', 'row'], + supportedCommands: ['eval', 'where', 'row', 'sort'], signatures: [ { params: [{ name: 'left', type: 'any' }], diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts index 84e85c4d6e797..861d18110d08f 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts @@ -330,13 +330,14 @@ export const commandDefinitions: CommandDefinition[] = [ '… | sort a desc, b nulls last, c asc nulls first', '… | sort b nulls last', '… | sort c asc nulls first', + '… | sort a - abs(b)', ], options: [], modes: [], signature: { multipleParams: true, params: [ - { name: 'column', type: 'column' }, + { name: 'expression', type: 'any' }, { name: 'direction', type: 'string', optional: true, values: ['asc', 'desc'] }, { name: 'nulls', type: 'string', optional: true, values: ['nulls first', 'nulls last'] }, ], diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts index 57dcb9acf93b1..d0b9110252b49 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts @@ -1692,7 +1692,7 @@ export const evalFunctionsDefinitions: FunctionDefinition[] = [ .sort(({ name: a }, { name: b }) => a.localeCompare(b)) .map((def) => ({ ...def, - supportedCommands: ['stats', 'eval', 'where', 'row'], + supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], type: 'eval', })); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index bbe3903c1aa07..6b8c666dd6b5b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -16092,6 +16092,509 @@ "error": [], "warning": [] }, + { + "query": "from a_index | sort abs(numberField) - to_long(stringField) desc nulls first", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort avg(numberField)", + "error": [ + "SORT does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | sort sum(numberField)", + "error": [ + "SORT does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | sort median(numberField)", + "error": [ + "SORT does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | sort median_absolute_deviation(numberField)", + "error": [ + "SORT does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | sort percentile(numberField, 5)", + "error": [ + "SORT does not support function percentile" + ], + "warning": [] + }, + { + "query": "from a_index | sort max(numberField)", + "error": [ + "SORT does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | sort min(numberField)", + "error": [ + "SORT does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | sort count(stringField)", + "error": [ + "SORT does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | sort count_distinct(stringField, numberField)", + "error": [ + "SORT does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | sort st_centroid_agg(cartesianPointField)", + "error": [ + "SORT does not support function st_centroid_agg" + ], + "warning": [] + }, + { + "query": "from a_index | sort values(stringField)", + "error": [ + "SORT does not support function values" + ], + "warning": [] + }, + { + "query": "from a_index | sort bucket(dateField, 1 year)", + "error": [ + "SORT does not support function bucket" + ], + "warning": [] + }, + { + "query": "from a_index | sort abs(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort acos(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort asin(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort atan(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort atan2(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort case(booleanField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort ceil(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort cidr_match(ipField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort coalesce(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort concat(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort cos(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort cosh(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort date_format(dateField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort date_parse(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort date_trunc(1 year, dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort e()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort ends_with(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort floor(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort greatest(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort least(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort left(stringField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort length(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort log(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort log10(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort ltrim(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_avg(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_concat(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_count(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_dedupe(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_first(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_last(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_max(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_median(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_min(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_slice(stringField, numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_sort(stringField, \"asc\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_sum(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort mv_zip(stringField, stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort now()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort pi()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort pow(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort replace(stringField, stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort right(stringField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort round(numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort rtrim(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort signum(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort sin(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort sinh(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort split(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort sqrt(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_contains(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_disjoint(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_intersects(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_within(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_x(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort st_y(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort starts_with(stringField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort substring(stringField, numberField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort tan(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort tanh(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_boolean(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_cartesianpoint(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_cartesianshape(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_datetime(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_degrees(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_double(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_geopoint(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_geoshape(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_integer(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_ip(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_long(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_lower(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_radians(numberField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_string(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_unsigned_long(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_upper(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort to_version(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort trim(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort sin(stringField)", + "error": [ + "Argument of [sin] must be [number], found value [stringField] type [string]" + ], + "warning": [] + }, + { + "query": "from a_index | sort numberField + stringField", + "error": [ + "Argument of [+] must be [number], found value [stringField] type [string]" + ], + "warning": [] + }, { "query": "from a_index | enrich", "error": [ diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 63abfc9dff036..dc37f5b2fd1ff 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -2455,6 +2455,58 @@ describe('validation logic', () => { } testErrorsAndWarnings(`row a = 1 | stats COUNT(*) | sort \`COUNT(*)\``, []); testErrorsAndWarnings(`ROW a = 1 | STATS couNt(*) | SORT \`couNt(*)\``, []); + + describe('sorting by expressions', () => { + // SORT accepts complex expressions + testErrorsAndWarnings( + 'from a_index | sort abs(numberField) - to_long(stringField) desc nulls first', + [] + ); + + // SORT doesn't accept agg or grouping functions + for (const definition of [ + ...statsAggregationFunctionDefinitions, + ...groupingFunctionDefinitions, + ]) { + const { + name, + signatures: [firstSignature], + } = definition; + const fieldMapping = getFieldMapping(firstSignature.params); + const printedInvocation = getFunctionSignatures( + { ...definition, signatures: [{ ...firstSignature, params: fieldMapping }] }, + { withTypes: false } + )[0].declaration; + + testErrorsAndWarnings(`from a_index | sort ${printedInvocation}`, [ + `SORT does not support function ${name}`, + ]); + } + + // But does accept eval functions + for (const definition of evalFunctionsDefinitions) { + const { + signatures: [firstSignature], + } = definition; + const fieldMapping = getFieldMapping(firstSignature.params); + const printedInvocation = getFunctionSignatures( + { ...definition, signatures: [{ ...firstSignature, params: fieldMapping }] }, + { withTypes: false } + )[0].declaration; + + testErrorsAndWarnings(`from a_index | sort ${printedInvocation}`, []); + } + + // Expression parts are also validated + testErrorsAndWarnings('from a_index | sort sin(stringField)', [ + 'Argument of [sin] must be [number], found value [stringField] type [string]', + ]); + + // Expression parts are also validated + testErrorsAndWarnings('from a_index | sort numberField + stringField', [ + 'Argument of [+] must be [number], found value [stringField] type [string]', + ]); + }); }); describe('enrich', () => { diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index 771d60fb573aa..f4a30dac6cb12 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -73,7 +73,7 @@ export const CONTEXT_DEFAULT_SIZE_ID = 'context:defaultSize'; export const CONTEXT_STEP_ID = 'context:step'; export const CONTEXT_TIE_BREAKER_FIELDS_ID = 'context:tieBreakerFields'; export const DEFAULT_COLUMNS_ID = 'defaultColumns'; -export const DISCOVER_ENABLE_ESQL_ID = 'discover:enableESQL'; +export const ENABLE_ESQL_ID = 'enableESQL'; export const DISCOVER_MAX_DOC_FIELDS_DISPLAYED_ID = 'discover:maxDocFieldsDisplayed'; export const DISCOVER_MODIFY_COLUMNS_ON_SWITCH_ID = 'discover:modifyColumnsOnSwitch'; export const DISCOVER_ROW_HEIGHT_OPTION_ID = 'discover:rowHeightOption'; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0cbb1eff71a67..8ee32095e8789 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -31,7 +31,7 @@ pageLoadAssetSize: datasetQuality: 50624 dataViewEditor: 28082 dataViewFieldEditor: 27000 - dataViewManagement: 5176 + dataViewManagement: 5300 dataViews: 65000 dataVisualizer: 27530 devTools: 38637 @@ -51,7 +51,7 @@ pageLoadAssetSize: expressionLegacyMetricVis: 23121 expressionMetric: 22238 expressionMetricVis: 23121 - expressionPartitionVis: 29000 + expressionPartitionVis: 29700 expressionRepeatImage: 22341 expressionRevealImage: 25675 expressions: 140958 diff --git a/packages/kbn-sort-predicates/README.md b/packages/kbn-sort-predicates/README.md index bc1a2fa86b028..b2e3e07b8ff75 100644 --- a/packages/kbn-sort-predicates/README.md +++ b/packages/kbn-sort-predicates/README.md @@ -9,6 +9,7 @@ This package contains a flexible sorting function who supports the following typ * dates (both as number or ISO string) * ranges open and closed (number type only for now) * null and undefined (always sorted as last entries, no matter the direction) + * if it matters the difference: null values are sorted always before undefined ones * any multi-value version of the types above (version excluded) * for multi-values with different length it wins the first non-zero comparison (see note at the bottom) diff --git a/packages/kbn-sort-predicates/src/sorting.test.ts b/packages/kbn-sort-predicates/src/sorting.test.ts index e2fd1a9448c58..12a3c815d2412 100644 --- a/packages/kbn-sort-predicates/src/sorting.test.ts +++ b/packages/kbn-sort-predicates/src/sorting.test.ts @@ -46,6 +46,56 @@ function testSorting({ } describe('Data sorting criteria', () => { + describe('null rows', () => { + // in these tests it needs to skip the testSorting utility in order to pass null rows + // mind that [].sort() will never pass `undefined` values to the comparison function + // so we test it with null values instead + it('should not crash with null rows with strings', () => { + const datatable = ['a', 'b', 'c', 'd', '12']; + const datatableWithNulls = datatable.flatMap((v) => [{ a: v }, null]); + const criteria = getSortingCriteria('string', 'a', getMockFormatter()); + expect( + datatableWithNulls + .sort((a, b) => criteria(a, b, 'asc')) + .map((row) => (row == null ? row : row.a)) + ).toEqual(['12', 'a', 'b', 'c', 'd', ...Array(datatable.length).fill(null)]); + expect( + datatableWithNulls + .sort((a, b) => criteria(a, b, 'desc')) + .map((row) => (row == null ? row : row.a)) + ).toEqual(['d', 'c', 'b', 'a', '12', ...Array(datatable.length).fill(null)]); + }); + + it('should not crash with null rows with version', () => { + const datatable = ['1.21.0', '1.1.0', '1.112.0', '1.0.0', '__other__']; + const datatableWithNulls = datatable.flatMap((v) => [{ a: v }, null]); + const criteria = getSortingCriteria('version', 'a', getMockFormatter()); + expect( + datatableWithNulls + .sort((a, b) => criteria(a, b, 'asc')) + .map((row) => (row == null ? row : row.a)) + ).toEqual([ + '1.0.0', + '1.1.0', + '1.21.0', + '1.112.0', + ...Array(datatable.length).fill(null), + '__other__', + ]); + expect( + datatableWithNulls + .sort((a, b) => criteria(a, b, 'desc')) + .map((row) => (row == null ? row : row.a)) + ).toEqual([ + '1.112.0', + '1.21.0', + '1.1.0', + '1.0.0', + ...Array(datatable.length).fill(null), + '__other__', + ]); + }); + }); describe('Date values', () => { for (const direction of ['asc', 'desc'] as const) { it(`should provide the date criteria for date values (${direction})`, () => { @@ -229,7 +279,7 @@ describe('Data sorting criteria', () => { it('should sort non-version stuff to the end', () => { testSorting({ input: ['1.21.0', undefined, '1.1.0', null, '1.112.0', '__other__', '1.0.0'], - output: ['1.0.0', '1.1.0', '1.21.0', '1.112.0', undefined, null, '__other__'], + output: ['1.0.0', '1.1.0', '1.21.0', '1.112.0', null, undefined, '__other__'], direction: 'asc', type: 'version', reverseOutput: false, @@ -237,7 +287,7 @@ describe('Data sorting criteria', () => { testSorting({ input: ['1.21.0', undefined, '1.1.0', null, '1.112.0', '__other__', '1.0.0'], - output: ['1.112.0', '1.21.0', '1.1.0', '1.0.0', undefined, null, '__other__'], + output: ['1.112.0', '1.21.0', '1.1.0', '1.0.0', null, undefined, '__other__'], direction: 'desc', type: 'version', reverseOutput: false, diff --git a/packages/kbn-sort-predicates/src/sorting.ts b/packages/kbn-sort-predicates/src/sorting.ts index e9fed6e5cfc0d..e9b2c1acb9fe6 100644 --- a/packages/kbn-sort-predicates/src/sorting.ts +++ b/packages/kbn-sort-predicates/src/sorting.ts @@ -133,11 +133,17 @@ function getSafeIpAddress(ip: string | undefined, directionFactor: number) { } const versionComparison: CompareFn = (v1, v2, direction) => { - const valueA = String(v1 ?? ''); - const valueB = String(v2 ?? ''); + const valueA = String(v1 == null ? '' : v1); + const valueB = String(v2 == null ? '' : v2); const aInvalid = !valueA || !valid(valueA); const bInvalid = !valueB || !valid(valueB); if (aInvalid && bInvalid) { + if (v1 == null && v1 !== v2) { + return direction * -1; + } + if (v2 == null && v1 !== v2) { + return direction * 1; + } return 0; } // need to fight the direction multiplication of the parent function @@ -164,30 +170,32 @@ const rangeComparison: CompareFn> = (v1, v2) => { function createArrayValuesHandler(sortBy: string, formatter: FieldFormat) { return function (criteriaFn: CompareFn) { return ( - rowA: Record, - rowB: Record, + rowA: Record | undefined | null, + rowB: Record | undefined | null, direction: 'asc' | 'desc' ) => { // handle the direction with a multiply factor. const directionFactor = direction === 'asc' ? 1 : -1; + // make it handle null/undefined values + // this masks null/undefined rows into null/undefined values so it can benefit from shared invalid logic + // and enable custom sorting for invalid values (like for version type) + const valueA = rowA == null ? rowA : rowA[sortBy]; + const valueB = rowB == null ? rowB : rowB[sortBy]; // if either side of the comparison is an array, make it also the other one become one // then perform an array comparison - if (Array.isArray(rowA[sortBy]) || Array.isArray(rowB[sortBy])) { + if (Array.isArray(valueA) || Array.isArray(valueB)) { return ( directionFactor * compareArrays( - (Array.isArray(rowA[sortBy]) ? rowA[sortBy] : [rowA[sortBy]]) as T[], - (Array.isArray(rowB[sortBy]) ? rowB[sortBy] : [rowB[sortBy]]) as T[], + (Array.isArray(valueA) ? valueA : [valueA]) as T[], + (Array.isArray(valueB) ? valueB : [valueB]) as T[], directionFactor, formatter, criteriaFn ) ); } - return ( - directionFactor * - criteriaFn(rowA[sortBy] as T, rowB[sortBy] as T, directionFactor, formatter) - ); + return directionFactor * criteriaFn(valueA as T, valueB as T, directionFactor, formatter); }; }; } @@ -201,12 +209,12 @@ function getUndefinedHandler( ) => number ) { return ( - rowA: Record, - rowB: Record, + rowA: Record | undefined | null, + rowB: Record | undefined | null, direction: 'asc' | 'desc' ) => { - const valueA = rowA[sortBy]; - const valueB = rowB[sortBy]; + const valueA = rowA?.[sortBy]; + const valueB = rowB?.[sortBy]; // do not use the utility above as null at root level is handled differently // than null/undefined within an array type if (valueA == null || Number.isNaN(valueA)) { @@ -218,7 +226,7 @@ function getUndefinedHandler( if (valueB == null || Number.isNaN(valueB)) { return -1; } - return sortingCriteria(rowA, rowB, direction); + return sortingCriteria(rowA!, rowB!, direction); }; } @@ -226,7 +234,11 @@ export function getSortingCriteria( type: string | undefined, sortBy: string, formatter: FieldFormat -) { +): ( + rowA: Record | undefined | null, + rowB: Record | undefined | null, + direction: 'asc' | 'desc' +) => number { const arrayValueHandler = createArrayValuesHandler(sortBy, formatter); if (type === 'date') { diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 817a340dc3a8f..79502207aea00 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -60,6 +60,7 @@ module.exports = (_, argv) => { // modules from npm '@elastic/charts', '@elastic/eui', + '@elastic/eui/optimize/es/components/provider/nested', '@elastic/eui/optimize/es/services', '@elastic/eui/optimize/es/services/format', '@elastic/eui/dist/eui_charts_theme', diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 519c706e723fd..78ee1229da4e9 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -71,6 +71,8 @@ const externals = { '@elastic/charts': '__kbnSharedDeps__.ElasticCharts', '@kbn/datemath': '__kbnSharedDeps__.KbnDatemath', '@elastic/eui': '__kbnSharedDeps__.ElasticEui', + '@elastic/eui/lib/components/provider/nested': + '__kbnSharedDeps__.ElasticEuiLibComponentsUseIsNestedEuiProvider', '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', '@elastic/eui/lib/services/format': '__kbnSharedDeps__.ElasticEuiLibServicesFormat', '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index 1046f2fed7cb2..b012cb5660113 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -39,6 +39,7 @@ export const Rxjs = require('rxjs'); export const ElasticNumeral = require('@elastic/numeral'); export const ElasticCharts = require('@elastic/charts'); export const ElasticEui = require('@elastic/eui'); +export const ElasticEuiLibComponentsUseIsNestedEuiProvider = require('@elastic/eui/optimize/es/components/provider/nested'); export const ElasticEuiLibServices = require('@elastic/eui/optimize/es/services'); export const ElasticEuiLibServicesFormat = require('@elastic/eui/optimize/es/services/format'); export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 720663374cf72..52027ffe90839 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -43,6 +43,11 @@ export { type PublishesUnifiedSearch, type PublishesWritableUnifiedSearch, } from './interfaces/fetch/publishes_unified_search'; +export { + apiHasAppContext, + type HasAppContext, + type EmbeddableAppContext, +} from './interfaces/has_app_context'; export { apiHasDisableTriggers, type HasDisableTriggers } from './interfaces/has_disable_triggers'; export { hasEditCapabilities, type HasEditCapabilities } from './interfaces/has_edit_capabilities'; export { diff --git a/packages/presentation/presentation_publishing/interfaces/has_app_context.ts b/packages/presentation/presentation_publishing/interfaces/has_app_context.ts new file mode 100644 index 0000000000000..e5ee43f0e7630 --- /dev/null +++ b/packages/presentation/presentation_publishing/interfaces/has_app_context.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 interface EmbeddableAppContext { + /** + * Current app's path including query and hash starting from {appId} + */ + getCurrentPath?: () => string; + currentAppId: string; +} + +export interface HasAppContext { + getAppContext: () => EmbeddableAppContext; +} + +export const apiHasAppContext = (unknownApi: unknown): unknownApi is HasAppContext => { + return ( + (unknownApi as HasAppContext).getAppContext !== undefined && + typeof (unknownApi as HasAppContext).getAppContext === 'function' + ); +}; diff --git a/packages/presentation/presentation_publishing/interfaces/has_edit_capabilities.ts b/packages/presentation/presentation_publishing/interfaces/has_edit_capabilities.ts index 24ce82529419c..b404b6afa0d97 100644 --- a/packages/presentation/presentation_publishing/interfaces/has_edit_capabilities.ts +++ b/packages/presentation/presentation_publishing/interfaces/has_edit_capabilities.ts @@ -15,9 +15,9 @@ import { HasTypeDisplayName } from './has_type'; * edited, and an isEditingEnabled function. */ export interface HasEditCapabilities extends HasTypeDisplayName { - onEdit: () => void; + onEdit: () => Promise; isEditingEnabled: () => boolean; - getEditHref?: () => string | undefined; + getEditHref?: () => Promise; } /** diff --git a/packages/react/kibana_context/root/root_provider.tsx b/packages/react/kibana_context/root/root_provider.tsx index 19cce446e5f11..f979d0ef49f5d 100644 --- a/packages/react/kibana_context/root/root_provider.tsx +++ b/packages/react/kibana_context/root/root_provider.tsx @@ -6,10 +6,16 @@ * Side Public License, v 1. */ -import type { I18nStart } from '@kbn/core-i18n-browser'; -import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import React, { FC, PropsWithChildren } from 'react'; +import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; +import type { I18nStart } from '@kbn/core-i18n-browser'; + +// @ts-expect-error EUI exports this component internally, but Kibana isn't picking it up its types +import { useIsNestedEuiProvider } from '@elastic/eui/lib/components/provider/nested'; +// @ts-expect-error EUI exports this component internally, but Kibana isn't picking it up its types +import { emitEuiProviderWarning } from '@elastic/eui/lib/services/theme/warning'; + import { KibanaEuiProvider, type KibanaEuiProviderProps } from './eui_provider'; /** Props for the KibanaRootContextProvider */ @@ -38,8 +44,19 @@ export const KibanaRootContextProvider: FC ( - - {children} - -); +}) => { + const hasEuiProvider = useIsNestedEuiProvider(); + + if (hasEuiProvider) { + emitEuiProviderWarning( + 'KibanaRootContextProvider has likely been nested in this React tree, either by direct reference or by KibanaRenderContextProvider. The result of this nesting is a nesting of EuiProvider, which has negative effects. Check your React tree for nested Kibana context providers.' + ); + return {children}; + } else { + return ( + + {children} + + ); + } +}; diff --git a/packages/react/kibana_context/theme/theme_provider.tsx b/packages/react/kibana_context/theme/theme_provider.tsx index 1a3bd0f8c1ce2..78b94c1e98793 100644 --- a/packages/react/kibana_context/theme/theme_provider.tsx +++ b/packages/react/kibana_context/theme/theme_provider.tsx @@ -11,9 +11,9 @@ import useObservable from 'react-use/lib/useObservable'; import { EuiThemeProvider, EuiThemeProviderProps } from '@elastic/eui'; -// @ts-ignore EUI exports this component internally, but Kibana isn't picking it up its types +// @ts-expect-error EUI exports this component internally, but Kibana isn't picking it up its types import { useIsNestedEuiProvider } from '@elastic/eui/lib/components/provider/nested'; -// @ts-ignore EUI exports this component internally, but Kibana isn't picking it up its types +// @ts-expect-error EUI exports this component internally, but Kibana isn't picking it up its types import { emitEuiProviderWarning } from '@elastic/eui/lib/services/theme/warning'; import { KibanaEuiProvider } from '@kbn/react-kibana-context-root'; diff --git a/src/core/server/integration_tests/http/cookie_session_storage.test.ts b/src/core/server/integration_tests/http/cookie_session_storage.test.ts index 90343da759ccb..4a71502db0ccb 100644 --- a/src/core/server/integration_tests/http/cookie_session_storage.test.ts +++ b/src/core/server/integration_tests/http/cookie_session_storage.test.ts @@ -439,7 +439,7 @@ describe('Cookie based SessionStorage', () => { await server.preboot(prebootDeps); const { server: innerServer } = await server.setup(setupDeps); - expect( + await expect( createCookieSessionStorageFactory(logger.get(), innerServer, { ...cookieOptions, sameSite: 'None', diff --git a/src/core/server/integration_tests/http/oas.test.ts b/src/core/server/integration_tests/http/oas.test.ts index d0adbb0d29ea7..602b9c76cefa2 100644 --- a/src/core/server/integration_tests/http/oas.test.ts +++ b/src/core/server/integration_tests/http/oas.test.ts @@ -67,7 +67,7 @@ afterEach(async () => { it('is disabled by default', async () => { const server = await startService(); - supertest(server.listener).get('/api/oas').expect(404); + await supertest(server.listener).get('/api/oas').expect(404); }); it('handles requests when enabled', async () => { diff --git a/src/core/server/integration_tests/http/router.test.ts b/src/core/server/integration_tests/http/router.test.ts index 42cd093244a48..e7640e39c3263 100644 --- a/src/core/server/integration_tests/http/router.test.ts +++ b/src/core/server/integration_tests/http/router.test.ts @@ -333,15 +333,15 @@ describe('Options', () => { let i = 0; const intervalId = setInterval(() => { if (i < body.length) { - request.write(body[i++]); + void request.write(body[i++]); } else { clearInterval(intervalId); - request.end((err, res) => { + void request.end((err, res) => { resolve(res); }); } }, interval); - request.on('error', (err) => { + void request.on('error', (err) => { clearInterval(intervalId); reject(err); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts index 026eaa462aa54..7f04f37589f69 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/cleanup.test.ts @@ -131,13 +131,13 @@ const setupBaseline = async () => { // inject corrupt saved objects directly using esClient await Promise.all( - savedObjects.map((savedObject) => { + savedObjects.map((savedObject) => client.create({ index: defaultKibanaIndex, refresh: 'wait_for', ...savedObject, - }); - }) + }) + ) ); return client; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index 7a2a8b06c9b1c..2b6c7136519ff 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -425,7 +425,7 @@ describe('migration actions', () => { const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); expect(redStatusResponse.status).toBe('red'); - client.indices.putSettings({ + void client.indices.putSettings({ index: 'red_then_yellow_index', body: { // Enable all shard allocation so that the index status turns yellow @@ -574,7 +574,7 @@ describe('migration actions', () => { let indexGreen = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'clone_red_then_green_index', body: { // Enable all shard allocation so that the index status goes green @@ -1844,7 +1844,7 @@ describe('migration actions', () => { let indexYellow = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'red_then_yellow_index', body: { // Renable allocation so that the status becomes yellow @@ -1897,7 +1897,7 @@ describe('migration actions', () => { let indexGreen = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'yellow_then_green_index', body: { // Set 0 replican so that this index becomes green diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts index a16041a73011e..c114293823cb7 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts @@ -464,7 +464,7 @@ export const runActionTestSuite = ({ const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); expect(redStatusResponse.status).toBe('red'); - client.indices.putSettings({ + void client.indices.putSettings({ index: 'red_then_yellow_index', body: { // Enable all shard allocation so that the index status turns yellow @@ -619,7 +619,7 @@ export const runActionTestSuite = ({ let indexGreen = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'clone_red_then_green_index', body: { // Enable all shard allocation so that the index status goes green @@ -1898,7 +1898,7 @@ export const runActionTestSuite = ({ let indexYellow = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'red_then_yellow_index', body: { // Renable allocation so that the status becomes yellow @@ -1951,7 +1951,7 @@ export const runActionTestSuite = ({ let indexGreen = false; setTimeout(() => { - client.indices.putSettings({ + void client.indices.putSettings({ index: 'yellow_then_green_index', body: { // Set 0 replican so that this index becomes green diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts index 4101c22c23d50..52a1d68255332 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/default_search_fields.test.ts @@ -11,9 +11,9 @@ import { createRoot } from '@kbn/core-test-helpers-kbn-server'; describe('SO default search fields', () => { let root: ReturnType; - afterEach(() => { + afterEach(async () => { try { - root?.shutdown(); + await root?.shutdown(); } catch (e) { /* trap */ } diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/split_failed_to_clone.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/split_failed_to_clone.test.ts index 1642e4059a885..306c91aae5902 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/split_failed_to_clone.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/split_failed_to_clone.test.ts @@ -132,14 +132,14 @@ describe.skip('when splitting .kibana into multiple indices and one clone fails' }); // cause a failure when cloning .kibana_slow_clone_* indices - client.cluster.putSettings({ persistent: { 'cluster.max_shards_per_node': 15 } }); + void client.cluster.putSettings({ persistent: { 'cluster.max_shards_per_node': 15 } }); await expect(runMigrationsWhichFailsWhenCloning()).rejects.toThrowError( /cluster_shard_limit_exceeded/ ); // remove the failure - client.cluster.putSettings({ persistent: { 'cluster.max_shards_per_node': 20 } }); + void client.cluster.putSettings({ persistent: { 'cluster.max_shards_per_node': 20 } }); const { runMigrations: runMigrations2ndTime } = await migratorTestKitFactory(); await runMigrations2ndTime(); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index 6b3d97206a094..43f0d03b86552 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -149,9 +149,9 @@ const previouslyRegisteredTypes = [ describe('SO type registrations', () => { let root: ReturnType; - afterEach(() => { + afterEach(async () => { try { - root?.shutdown(); + await root?.shutdown(); } catch (e) { /* trap */ } diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/wait_for_migration_completion.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/wait_for_migration_completion.test.ts index 7d2a2bf8145cf..459cfc921badf 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/wait_for_migration_completion.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/wait_for_migration_completion.test.ts @@ -60,7 +60,7 @@ describe('migration with waitForCompletion=true', () => { await root.preboot(); await root.setup(); - root.start(); + void root.start(); const esClient = esServer.es.getClient(); await retryAsync( diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index db89ae88068fb..1440d6d9d91d3 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -123,7 +123,7 @@ export const getEsClient = async ({ // configure logging system const loggingConf = await firstValueFrom(configService.atPath('logging')); - loggingSystem.upgrade(loggingConf); + await loggingSystem.upgrade(loggingConf); return await getElasticsearchClient(configService, loggerFactory, kibanaVersion); }; @@ -148,7 +148,7 @@ export const getKibanaMigratorTestKit = async ({ // configure logging system const loggingConf = await firstValueFrom(configService.atPath('logging')); - loggingSystem.upgrade(loggingConf); + await loggingSystem.upgrade(loggingConf); const rawClient = await getElasticsearchClient(configService, loggerFactory, kibanaVersion); const client = clientWrapperFactory ? clientWrapperFactory(rawClient) : rawClient; @@ -568,7 +568,7 @@ export const getCurrentVersionTypeRegistry = async ({ await root.preboot(); const coreSetup = await root.setup(); const typeRegistry = coreSetup.savedObjects.getTypeRegistry(); - root.shutdown(); // do not await for it, or we might block the tests + void root.shutdown(); // do not await for it, or we might block the tests return typeRegistry; }; diff --git a/src/core/server/integration_tests/saved_objects/validation/validator.test.ts b/src/core/server/integration_tests/saved_objects/validation/validator.test.ts index 63909b14c290f..a0dc22440f1df 100644 --- a/src/core/server/integration_tests/saved_objects/validation/validator.test.ts +++ b/src/core/server/integration_tests/saved_objects/validation/validator.test.ts @@ -186,7 +186,7 @@ describe.skip('validates saved object types when a schema is provided', () => { }); it('is superseded by migration errors and does not run if a migration fails', async () => { - expect(async () => { + await expect(async () => { await savedObjectsClient.create( 'migration-error', { @@ -233,7 +233,7 @@ describe.skip('validates saved object types when a schema is provided', () => { describe('when validating with a config schema', () => { it('throws when an invalid attribute is provided', async () => { - expect(async () => { + await expect(async () => { await savedObjectsClient.create( 'schema-using-kbn-config', { diff --git a/src/dev/eslint/types.eslint.config.template.js b/src/dev/eslint/types.eslint.config.template.js index 08cbaf3b02325..db17de6f08e3e 100644 --- a/src/dev/eslint/types.eslint.config.template.js +++ b/src/dev/eslint/types.eslint.config.template.js @@ -24,4 +24,14 @@ module.exports = { rules: { '@typescript-eslint/consistent-type-exports': 'error', }, + overrides: [ + { + files: ['server/**/*'], + rules: { + // Let's focus on server-side errors first to avoid server crashes. + // We'll tackle /public eventually. + '@typescript-eslint/no-floating-promises': 'error', + }, + }, + ], }; diff --git a/src/plugins/content_management/server/core/core.test.ts b/src/plugins/content_management/server/core/core.test.ts index 2eafd1dc4df91..075eb6a383e8b 100644 --- a/src/plugins/content_management/server/core/core.test.ts +++ b/src/plugins/content_management/server/core/core.test.ts @@ -309,7 +309,7 @@ describe('Content Core', () => { { title: 'Hello' }, { id: '1234' } // We send this "id" option to specify the id of the content created ); - expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ + await expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ contentTypeId: FOO_CONTENT_ID, result: { item: { @@ -327,7 +327,7 @@ describe('Content Core', () => { await fooContentCrud!.create(ctx, { title: 'Hello' }, { id: '1234' }); await fooContentCrud!.update(ctx, '1234', { title: 'changed' }); - expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ + await expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ contentTypeId: FOO_CONTENT_ID, result: { item: { @@ -363,7 +363,7 @@ describe('Content Core', () => { }, }); - expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ + await expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ contentTypeId: FOO_CONTENT_ID, result: { item: { @@ -380,14 +380,14 @@ describe('Content Core', () => { const { fooContentCrud, ctx, cleanUp } = setup({ registerFooType: true }); await fooContentCrud!.create(ctx, { title: 'Hello' }, { id: '1234' }); - expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ + await expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ contentTypeId: FOO_CONTENT_ID, result: { item: expect.any(Object), }, }); await fooContentCrud!.delete(ctx, '1234'); - expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ + await expect(fooContentCrud!.get(ctx, '1234')).resolves.toEqual({ contentTypeId: FOO_CONTENT_ID, result: { item: undefined, diff --git a/src/plugins/content_management/server/rpc/procedures/bulk_get.test.ts b/src/plugins/content_management/server/rpc/procedures/bulk_get.test.ts index baeb9c08471c7..d79af15b136d2 100644 --- a/src/plugins/content_management/server/rpc/procedures/bulk_get.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/bulk_get.test.ts @@ -264,16 +264,16 @@ describe('RPC -> bulkGet()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => fn(ctx, { contentTypeId: 'unknown', ids: ['123', '456'] })).rejects.toEqual( - new Error('Content [unknown] is not registered.') - ); + await expect(() => + fn(ctx, { contentTypeId: 'unknown', ids: ['123', '456'] }) + ).rejects.toEqual(new Error('Content [unknown] is not registered.')); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, ids: ['123', '456'], @@ -284,9 +284,9 @@ describe('RPC -> bulkGet()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { contentTypeId: FOO_CONTENT_ID, ids: ['1234'], version: 1 }); + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, ids: ['1234'], version: 1 }); const [[storageContext]] = storage.bulkGet.mock.calls; // getTransforms() utils should be available from context diff --git a/src/plugins/content_management/server/rpc/procedures/create.test.ts b/src/plugins/content_management/server/rpc/procedures/create.test.ts index ec877d2ea0d0d..5bf8a63462f69 100644 --- a/src/plugins/content_management/server/rpc/procedures/create.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/create.test.ts @@ -230,16 +230,16 @@ describe('RPC -> create()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: 'unknown', data: { title: 'Hello' } }) ).rejects.toEqual(new Error('Content [unknown] is not registered.')); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, data: { title: 'Hello' }, @@ -250,9 +250,9 @@ describe('RPC -> create()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { contentTypeId: FOO_CONTENT_ID, data: { title: 'Hello' }, version: 1 }); + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, data: { title: 'Hello' }, version: 1 }); const [[storageContext]] = storage.create.mock.calls; // getTransforms() utils should be available from context diff --git a/src/plugins/content_management/server/rpc/procedures/delete.test.ts b/src/plugins/content_management/server/rpc/procedures/delete.test.ts index 5becd91a7b7d4..0d9089eecc19b 100644 --- a/src/plugins/content_management/server/rpc/procedures/delete.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/delete.test.ts @@ -210,16 +210,16 @@ describe('RPC -> delete()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => fn(ctx, { contentTypeId: 'unknown', id: '1234' })).rejects.toEqual( + await expect(() => fn(ctx, { contentTypeId: 'unknown', id: '1234' })).rejects.toEqual( new Error('Content [unknown] is not registered.') ); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', @@ -230,9 +230,9 @@ describe('RPC -> delete()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 }); + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 }); const [[storageContext]] = storage.delete.mock.calls; // getTransforms() utils should be available from context diff --git a/src/plugins/content_management/server/rpc/procedures/get.test.ts b/src/plugins/content_management/server/rpc/procedures/get.test.ts index 83affb9e725ae..6a304c325cd13 100644 --- a/src/plugins/content_management/server/rpc/procedures/get.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/get.test.ts @@ -215,16 +215,16 @@ describe('RPC -> get()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => fn(ctx, { contentTypeId: 'unknown', id: '1234' })).rejects.toEqual( + await expect(() => fn(ctx, { contentTypeId: 'unknown', id: '1234' })).rejects.toEqual( new Error('Content [unknown] is not registered.') ); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', @@ -235,9 +235,9 @@ describe('RPC -> get()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 }); + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '1234', version: 1 }); const [[storageContext]] = storage.get.mock.calls; // getTransforms() utils should be available from context diff --git a/src/plugins/content_management/server/rpc/procedures/msearch.test.ts b/src/plugins/content_management/server/rpc/procedures/msearch.test.ts index 7b28fae6d3b49..0cf899a59ae36 100644 --- a/src/plugins/content_management/server/rpc/procedures/msearch.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/msearch.test.ts @@ -245,9 +245,9 @@ describe('RPC -> mSearch()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypes: [{ contentTypeId: 'unknown', version: 1 }], query: { text: 'Hello' }, @@ -255,9 +255,9 @@ describe('RPC -> mSearch()', () => { ).rejects.toEqual(new Error('Content [unknown] is not registered.')); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypes: [{ contentTypeId: 'foo', version: 7 }], query: { text: 'Hello' }, diff --git a/src/plugins/content_management/server/rpc/procedures/search.test.ts b/src/plugins/content_management/server/rpc/procedures/search.test.ts index 2d7ef1501d102..0ee18e68d2299 100644 --- a/src/plugins/content_management/server/rpc/procedures/search.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/search.test.ts @@ -247,16 +247,16 @@ describe('RPC -> search()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: 'unknown', query: { text: 'Hello' } }) ).rejects.toEqual(new Error('Content [unknown] is not registered.')); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, query: { text: 'Hello' }, @@ -267,9 +267,9 @@ describe('RPC -> search()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { contentTypeId: FOO_CONTENT_ID, query: { text: 'Hello' }, version: 1 }); + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, query: { text: 'Hello' }, version: 1 }); const [[storageContext]] = storage.search.mock.calls; // getTransforms() utils should be available from context diff --git a/src/plugins/content_management/server/rpc/procedures/update.test.ts b/src/plugins/content_management/server/rpc/procedures/update.test.ts index ad721d045be1d..48d8a846961a5 100644 --- a/src/plugins/content_management/server/rpc/procedures/update.test.ts +++ b/src/plugins/content_management/server/rpc/procedures/update.test.ts @@ -240,16 +240,16 @@ describe('RPC -> update()', () => { }); describe('validation', () => { - test('should validate that content type definition exist', () => { + test('should validate that content type definition exist', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: 'unknown', id: '123', data: { title: 'Hello' } }) ).rejects.toEqual(new Error('Content [unknown] is not registered.')); }); - test('should throw if the request version is higher than the registered version', () => { + test('should throw if the request version is higher than the registered version', async () => { const { ctx } = setup(); - expect(() => + await expect(() => fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '123', @@ -261,9 +261,9 @@ describe('RPC -> update()', () => { }); describe('object versioning', () => { - test('should expose a utility to transform and validate services objects', () => { + test('should expose a utility to transform and validate services objects', async () => { const { ctx, storage } = setup(); - fn(ctx, { + await fn(ctx, { contentTypeId: FOO_CONTENT_ID, id: '123', version: 1, diff --git a/src/plugins/content_management/server/rpc/rpc_service.test.ts b/src/plugins/content_management/server/rpc/rpc_service.test.ts index 0ac81a3c3fa8f..197c1f257b585 100644 --- a/src/plugins/content_management/server/rpc/rpc_service.test.ts +++ b/src/plugins/content_management/server/rpc/rpc_service.test.ts @@ -11,21 +11,21 @@ import { ProcedureDefinition, RpcService } from './rpc_service'; describe('RpcService', () => { describe('register()', () => { - test('should register a procedure', () => { + test('should register a procedure', async () => { const rpc = new RpcService<{}, 'foo'>(); const fn = jest.fn(); const procedure: ProcedureDefinition<{}> = { fn }; rpc.register('foo', procedure); const context = {}; - rpc.call(context, 'foo'); + await rpc.call(context, 'foo'); expect(fn).toHaveBeenCalledWith(context, undefined); }); }); describe('call()', () => { - test('should require a schema if an input is passed', () => { + test('should require a schema if an input is passed', async () => { const rpc = new RpcService<{}, 'foo'>(); const fn = jest.fn(); const procedure: ProcedureDefinition<{}> = { fn }; @@ -34,7 +34,7 @@ describe('RpcService', () => { const context = {}; const input = { foo: 'bar' }; - expect(() => { + await expect(() => { return rpc.call(context, 'foo', input); }).rejects.toEqual(new Error('Input schema missing for [foo] procedure.')); }); @@ -60,15 +60,15 @@ describe('RpcService', () => { expect(result).toEqual(output); }); - test('should throw an error if the procedure is not registered', () => { + test('should throw an error if the procedure is not registered', async () => { const rpc = new RpcService(); - expect(() => { + await expect(() => { return rpc.call(undefined, 'unknown'); }).rejects.toEqual(new Error('Procedure [unknown] is not registered.')); }); - test('should validate that the input is valid', () => { + test('should validate that the input is valid', async () => { const rpc = new RpcService<{}, 'foo'>(); const fn = jest.fn(); @@ -81,12 +81,12 @@ describe('RpcService', () => { const context = {}; const input = { bad: 'unknown prop' }; - expect(() => { + await expect(() => { return rpc.call(context, 'foo', input); }).rejects.toEqual(new Error('[foo]: expected value of type [string] but got [undefined]')); }); - test('should validate the output if schema is provided', () => { + test('should validate the output if schema is provided', async () => { const rpc = new RpcService<{}, 'foo'>(); const fn = jest.fn().mockResolvedValue({ bad: 'unknown prop' }); @@ -97,7 +97,7 @@ describe('RpcService', () => { rpc.register('foo', procedure); const context = {}; - expect(() => { + await expect(() => { return rpc.call(context, 'foo'); }).rejects.toEqual(new Error('[foo]: expected value of type [string] but got [undefined]')); }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 483de4df70577..23d677b3e47c5 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -306,7 +306,7 @@ export const initializeDashboard = async ({ // -------------------------------------------------------------------------------------- // Place the incoming embeddable if there is one // -------------------------------------------------------------------------------------- - const incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.(); + const incomingEmbeddable = creationOptions?.getIncomingEmbeddable?.() as any; if (incomingEmbeddable) { const scrolltoIncomingEmbeddable = (container: DashboardContainer, id: string) => { container.setScrollToPanelId(id); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx index c7bcc14ef6b25..6944edb5afa7a 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container_factory.tsx @@ -14,13 +14,13 @@ import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common import { Container, ContainerOutput, - EmbeddableAppContext, EmbeddableFactory, EmbeddableFactoryDefinition, EmbeddablePackageState, ErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import { EmbeddableAppContext } from '@kbn/presentation-publishing'; import { DASHBOARD_CONTAINER_TYPE } from '..'; import { createExtract, createInject, DashboardContainerInput } from '../../../common'; diff --git a/src/plugins/data/common/search/poll_search.test.ts b/src/plugins/data/common/search/poll_search.test.ts index a884f1dad1558..6cc8fa34a2bb3 100644 --- a/src/plugins/data/common/search/poll_search.test.ts +++ b/src/plugins/data/common/search/poll_search.test.ts @@ -85,6 +85,25 @@ describe('pollSearch', () => { expect(cancelFn).toBeCalledTimes(1); }); + test('Does not leak unresolved promises on cancel', async () => { + const searchFn = getMockedSearch$(20); + const cancelFn = jest.fn().mockRejectedValueOnce({ error: 'Oh no!' }); + const abortController = new AbortController(); + const poll = pollSearch(searchFn, cancelFn, { + abortSignal: abortController.signal, + }).toPromise(); + + await new Promise((resolve) => setTimeout(resolve, 300)); + abortController.abort(); + + await expect(poll).rejects.toThrow(AbortError); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + expect(searchFn).toBeCalledTimes(1); + expect(cancelFn).toBeCalledTimes(1); + }); + test("Stops, but doesn't cancel on unsubscribe", async () => { const searchFn = getMockedSearch$(20); const cancelFn = jest.fn(); diff --git a/src/plugins/data/common/search/poll_search.ts b/src/plugins/data/common/search/poll_search.ts index 54985366b9127..3965ee96bf417 100644 --- a/src/plugins/data/common/search/poll_search.ts +++ b/src/plugins/data/common/search/poll_search.ts @@ -14,7 +14,7 @@ import { isAbortResponse, isRunningResponse } from '..'; export const pollSearch = ( search: () => Promise, - cancel?: () => void, + cancel?: () => Promise, { pollInterval, abortSignal }: IAsyncSearchOptions = {} ): Observable => { const getPollInterval = (elapsedTime: number): number => { @@ -41,8 +41,13 @@ export const pollSearch = ( throw new AbortError(); } + const safeCancel = () => + cancel?.().catch((e) => { + console.error(e); // eslint-disable-line no-console + }); + if (cancel) { - abortSignal?.addEventListener('abort', cancel, { once: true }); + abortSignal?.addEventListener('abort', safeCancel, { once: true }); } const aborted$ = (abortSignal ? fromEvent(abortSignal, 'abort') : EMPTY).pipe( diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 8c01ea615fd37..c6d07bc9e98c2 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -500,6 +500,50 @@ describe('SearchInterceptor', () => { expect(mockCoreSetup.http.delete).toHaveBeenCalledTimes(1); }); + test('should not leak unresolved promises if DELETE fails', async () => { + mockCoreSetup.http.delete.mockRejectedValueOnce({ status: 404, statusText: 'Not Found' }); + const responses = [ + { + time: 10, + value: { + isPartial: true, + isRunning: true, + rawResponse: {}, + id: 1, + }, + }, + { + time: 10, + value: { + statusCode: 500, + message: 'oh no', + id: 1, + }, + isError: true, + }, + ]; + mockFetchImplementation(responses); + + const response = searchInterceptor.search({}, { pollInterval: 0 }); + response.subscribe({ next, error }); + + await timeTravel(10); + + expect(next).toHaveBeenCalled(); + expect(error).not.toHaveBeenCalled(); + expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); + + // Long enough to reach the timeout but not long enough to reach the next response + await timeTravel(10); + + expect(error).toHaveBeenCalled(); + expect(error.mock.calls[0][0]).toBeInstanceOf(Error); + expect((error.mock.calls[0][0] as Error).message).toBe('oh no'); + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.delete).toHaveBeenCalledTimes(1); + }); + test('should NOT DELETE a running SAVED async search on abort', async () => { const sessionId = 'sessionId'; sessionService.isCurrentSession.mockImplementation((_sessionId) => _sessionId === sessionId); diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts index 5da9d4c2f7120..4b73f2ac9336b 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts @@ -339,11 +339,19 @@ export class SearchInterceptor { isSavedToBackground = true; }); - const sendCancelRequest = once(() => { - this.deps.http.delete(`/internal/search/${strategy}/${id}`, { version: '1' }); - }); + const sendCancelRequest = once(() => + this.deps.http.delete(`/internal/search/${strategy}/${id}`, { version: '1' }) + ); - const cancel = () => id && !isSavedToBackground && sendCancelRequest(); + const cancel = async () => { + if (!id || isSavedToBackground) return; + try { + await sendCancelRequest(); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + }; // Async search requires a series of requests // 1) POST //_async_search/ diff --git a/src/plugins/data/server/query/route_handler_context.test.ts b/src/plugins/data/server/query/route_handler_context.test.ts index 5976db550f182..d2a9a14fe1fce 100644 --- a/src/plugins/data/server/query/route_handler_context.test.ts +++ b/src/plugins/data/server/query/route_handler_context.test.ts @@ -207,12 +207,12 @@ describe('saved query route handler context', () => { const response = context.create(savedQueryAttributes); - expect(response).rejects.toMatchInlineSnapshot(`[Error: An Error]`); + await expect(response).rejects.toMatchInlineSnapshot(`[Error: An Error]`); }); it('should throw an error if the saved query does not have a title', async () => { const response = context.create({ ...savedQueryAttributes, title: '' }); - expect(response).rejects.toMatchInlineSnapshot( + await expect(response).rejects.toMatchInlineSnapshot( `[Error: Cannot create query without a title]` ); }); @@ -266,12 +266,12 @@ describe('saved query route handler context', () => { const response = context.update('foo', savedQueryAttributes); - expect(response).rejects.toMatchInlineSnapshot(`[Error: An Error]`); + await expect(response).rejects.toMatchInlineSnapshot(`[Error: An Error]`); }); it('should throw an error if the saved query does not have a title', async () => { const response = context.create({ ...savedQueryAttributes, title: '' }); - expect(response).rejects.toMatchInlineSnapshot( + await expect(response).rejects.toMatchInlineSnapshot( `[Error: Cannot create query without a title]` ); }); @@ -609,7 +609,7 @@ describe('saved query route handler context', () => { }); const result = context.get('food'); - expect(result).rejects.toMatchInlineSnapshot( + await expect(result).rejects.toMatchInlineSnapshot( `[Error: Multiple saved queries found with ID: food (legacy URL alias conflict)]` ); }); diff --git a/src/plugins/data/server/search/collectors/search/usage.ts b/src/plugins/data/server/search/collectors/search/usage.ts index 65eafd28538e3..50d71fbc8ddff 100644 --- a/src/plugins/data/server/search/collectors/search/usage.ts +++ b/src/plugins/data/server/search/collectors/search/usage.ts @@ -88,11 +88,11 @@ export function searchUsageObserver( next(response: IEsSearchResponse) { if (isRestore || isRunningResponse(response)) return; logger.debug(`trackSearchStatus:success, took:${response.rawResponse.took}`); - usage?.trackSuccess(response.rawResponse.took); + void usage?.trackSuccess(response.rawResponse.took); }, error(e: Error) { logger.debug(`trackSearchStatus:error, ${e}`); - usage?.trackError(); + void usage?.trackError(); }, }; } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index ad4dec61cab7c..12b84ca0c5694 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -256,7 +256,7 @@ export class SearchService implements Plugin { registerFunction: expressions.registerFunction, }); - firstValueFrom(this.initializerContext.config.create()).then((value) => { + void firstValueFrom(this.initializerContext.config.create()).then((value) => { if (value.search.aggs.shardDelay.enabled) { aggs.types.registerBucket(SHARD_DELAY_AGG_NAME, getShardDelayBucketAgg); expressions.registerFunction(aggShardDelay); diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 481fabcfa423e..212eb810f227e 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -102,31 +102,31 @@ describe('SearchSessionService', () => { expect(savedObjectsClient.create).not.toHaveBeenCalled(); }); - it('Save throws', () => { - expect(() => + it('Save throws', async () => { + await expect(() => service.save({ savedObjectsClient }, mockUser1, sessionId, {}) ).rejects.toBeInstanceOf(Error); }); - it('Update throws', () => { + it('Update throws', async () => { const attributes = { name: 'new_name' }; const response = service.update({ savedObjectsClient }, mockUser1, sessionId, attributes); - expect(response).rejects.toBeInstanceOf(Error); + await expect(response).rejects.toBeInstanceOf(Error); }); - it('Cancel throws', () => { + it('Cancel throws', async () => { const response = service.cancel({ savedObjectsClient }, mockUser1, sessionId); - expect(response).rejects.toBeInstanceOf(Error); + await expect(response).rejects.toBeInstanceOf(Error); }); - it('getId throws', () => { + it('getId throws', async () => { const response = service.getId({ savedObjectsClient }, mockUser1, {}, {}); - expect(response).rejects.toBeInstanceOf(Error); + await expect(response).rejects.toBeInstanceOf(Error); }); - it('Delete throws', () => { + it('Delete throws', async () => { const response = service.delete({ savedObjectsClient }, mockUser1, sessionId); - expect(response).rejects.toBeInstanceOf(Error); + await expect(response).rejects.toBeInstanceOf(Error); }); }); @@ -156,7 +156,7 @@ describe('SearchSessionService', () => { const coreStart = coreMock.createStart(); await flushPromises(); - await service.start(coreStart, {}); + service.start(coreStart, {}); }); afterEach(() => { @@ -164,22 +164,22 @@ describe('SearchSessionService', () => { }); describe('save', () => { - it('throws if `name` is not provided', () => { - expect(() => + it('throws if `name` is not provided', async () => { + await expect(() => service.save({ savedObjectsClient }, mockUser1, sessionId, {}) ).rejects.toMatchInlineSnapshot(`[Error: Name is required]`); }); - it('throws if `appId` is not provided', () => { - expect( + it('throws if `appId` is not provided', async () => { + await expect( service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana', }) ).rejects.toMatchInlineSnapshot(`[Error: AppId is required]`); }); - it('throws if `locatorId` is not provided', () => { - expect( + it('throws if `locatorId` is not provided', async () => { + await expect( service.save({ savedObjectsClient }, mockUser1, sessionId, { name: 'banana', appId: 'nanana', @@ -220,10 +220,10 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('username', mockUser1.username); }); - it('throws error if user conflicts', () => { + it('throws error if user conflicts', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); - expect( + await expect( service.get({ savedObjectsClient }, mockUser2, sessionId) ).rejects.toMatchInlineSnapshot(`[Error: Not Found]`); }); @@ -614,7 +614,7 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('name', attributes.name); }); - it('throws if user conflicts', () => { + it('throws if user conflicts', async () => { const mockUpdateSavedObject = { ...mockSavedObject, attributes: {}, @@ -623,7 +623,7 @@ describe('SearchSessionService', () => { savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); const attributes = { name: 'new_name' }; - expect( + await expect( service.update({ savedObjectsClient }, mockUser2, sessionId, attributes) ).rejects.toMatchInlineSnapshot(`[Error: Not Found]`); }); @@ -659,10 +659,10 @@ describe('SearchSessionService', () => { expect(callAttributes).toHaveProperty('isCanceled', true); }); - it('throws if user conflicts', () => { + it('throws if user conflicts', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); - expect( + await expect( service.cancel({ savedObjectsClient }, mockUser2, sessionId) ).rejects.toMatchInlineSnapshot(`[Error: Not Found]`); }); @@ -803,18 +803,18 @@ describe('SearchSessionService', () => { }); describe('getId', () => { - it('throws if `sessionId` is not provided', () => { + it('throws if `sessionId` is not provided', async () => { const searchRequest = { params: {} }; - expect(() => + await expect(() => service.getId({ savedObjectsClient }, mockUser1, searchRequest, {}) ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); }); - it('throws if there is not a saved object', () => { + it('throws if there is not a saved object', async () => { const searchRequest = { params: {} }; - expect(() => + await expect(() => service.getId({ savedObjectsClient }, mockUser1, searchRequest, { sessionId, isStored: false, @@ -824,10 +824,10 @@ describe('SearchSessionService', () => { ); }); - it('throws if not restoring a saved session', () => { + it('throws if not restoring a saved session', async () => { const searchRequest = { params: {} }; - expect(() => + await expect(() => service.getId({ savedObjectsClient }, mockUser1, searchRequest, { sessionId, isStored: true, diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts index b50ccc508dbfd..f5e655612bc9c 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts @@ -66,7 +66,8 @@ describe('ES search strategy', () => { const mockSubmitCaller = jest.fn(); const mockDeleteCaller = jest.fn(); const mockLogger: any = { - debug: () => {}, + debug: jest.fn(), + error: jest.fn(), }; const mockDeps = { uiSettingsClient: { @@ -357,6 +358,47 @@ describe('ES search strategy', () => { expect(err).not.toBeUndefined(); expect(mockDeleteCaller).toBeCalled(); }); + + it('should not throw when encountering an error deleting', async () => { + mockSubmitCaller.mockResolvedValueOnce({ + ...mockAsyncResponse, + body: { + ...mockAsyncResponse.body, + is_running: true, + }, + }); + + const errResponse = new errors.ResponseError({ + body: xContentParseException, + statusCode: 400, + headers: {}, + warnings: [], + meta: {} as any, + }); + mockDeleteCaller.mockRejectedValueOnce(errResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: KbnServerError | undefined; + try { + await esSearch.search({ params }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + expect(mockSubmitCaller).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockDeleteCaller).toBeCalled(); + }); }); describe('with sessionId', () => { diff --git a/src/plugins/data_views/server/expressions/load_index_pattern.test.ts b/src/plugins/data_views/server/expressions/load_index_pattern.test.ts index 94bd854e6734c..2c59a35349912 100644 --- a/src/plugins/data_views/server/expressions/load_index_pattern.test.ts +++ b/src/plugins/data_views/server/expressions/load_index_pattern.test.ts @@ -35,7 +35,7 @@ describe('indexPattern expression function', () => { test('throws if getKibanaRequest is not available', async () => { const indexPatternDefinition = getFunctionDefinition({ getStartDependencies }); - expect(async () => { + await expect(async () => { await indexPatternDefinition().fn(null, { id: '1' }, {} as any); }).rejects.toThrowErrorMatchingInlineSnapshot( `"A KibanaRequest is required to execute this search on the server. Please provide a request object to the expression execution params."` diff --git a/src/plugins/data_views/server/rest_api_routes/internal/fields.ts b/src/plugins/data_views/server/rest_api_routes/internal/fields.ts index 0536422476228..99ce0736a9ead 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/fields.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/fields.ts @@ -137,7 +137,7 @@ const handler: (isRollupsEnabled: () => boolean) => RequestHandler<{}, IQuery, I } }; -export const registerFields = async ( +export const registerFields = ( router: IRouter, getStartServices: StartServicesAccessor< DataViewsServerPluginStartDependencies, diff --git a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts index 439dc6795b682..0daadeaf567c3 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts @@ -207,7 +207,7 @@ const handler: (isRollupsEnabled: () => boolean) => RequestHandler<{}, IQuery, I } }; -export const registerFieldForWildcard = async ( +export const registerFieldForWildcard = ( router: IRouter, getStartServices: StartServicesAccessor< DataViewsServerPluginStartDependencies, diff --git a/src/plugins/data_views/server/rest_api_routes/public/create_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/create_data_view.test.ts index 206ca278a85d7..89e01734977e7 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/create_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/create_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('create data view', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - createDataView({ + await createDataView({ dataViewsService, spec: {}, counterName: 'POST /path', diff --git a/src/plugins/data_views/server/rest_api_routes/public/default_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/default_data_view.test.ts index 9af8688913229..f6ec3861f994f 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/default_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/default_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('default data view', () => { - it('set - calls usageCollection', () => { + it('set - calls usageCollection', async () => { const usageCollection = getUsageCollection(); - setDefault({ + await setDefault({ dataViewsService, counterName: 'POST /path', usageCollection, @@ -23,9 +23,9 @@ describe('default data view', () => { expect(usageCollection.incrementCounter).toBeCalledTimes(1); }); - it('get - calls usageCollection', () => { + it('get - calls usageCollection', async () => { const usageCollection = getUsageCollection(); - getDefault({ + await getDefault({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/delete_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/delete_data_view.test.ts index d1c41e8bc8d47..f3e902fa5be65 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/delete_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/delete_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('delete data view', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - deleteDataView({ + await deleteDataView({ dataViewsService, counterName: 'DELETE /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/fields/update_fields.test.ts b/src/plugins/data_views/server/rest_api_routes/public/fields/update_fields.test.ts index c2ff246568c84..06a4f89b25d85 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/fields/update_fields.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/fields/update_fields.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('create runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -27,7 +27,7 @@ describe('create runtime field', () => { } as unknown as DataViewLazy) ); - updateFields({ + await updateFields({ dataViewsService, counterName: 'POST /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/get_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/get_data_view.test.ts index b035d48072f4c..963e3a64ceeb5 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/get_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/get_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('get default data view', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - getDataView({ + await getDataView({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/get_data_views.test.ts b/src/plugins/data_views/server/rest_api_routes/public/get_data_views.test.ts index d3cd1d68af018..ae5ed06674d15 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/get_data_views.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/get_data_views.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('get all data views', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - getDataViews({ + await getDataViews({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/has_user_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/has_user_data_view.test.ts index fc2f763580e99..d2e549d7ded2b 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/has_user_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/has_user_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('get default data view', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - hasUserDataView({ + await hasUserDataView({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/create_runtime_field.test.ts b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/create_runtime_field.test.ts index 30ba66ec22b55..ba7f59c1b4474 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/create_runtime_field.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/create_runtime_field.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('create runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -28,7 +28,7 @@ describe('create runtime field', () => { } as unknown as DataViewLazy) ); - createRuntimeField({ + await createRuntimeField({ dataViewsService, counterName: 'POST /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/delete_runtime_field.test.ts b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/delete_runtime_field.test.ts index 6f0fb6ab168d8..955fbdff4ffab 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/delete_runtime_field.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/delete_runtime_field.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('delete runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -23,7 +23,7 @@ describe('delete runtime field', () => { } as unknown as DataViewLazy) ); - deleteRuntimeField({ + await deleteRuntimeField({ dataViewsService, counterName: 'DELETE /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/get_runtime_field.test.ts b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/get_runtime_field.test.ts index 8693c78961bfc..c5080a16178fc 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/get_runtime_field.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/get_runtime_field.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('get runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -25,7 +25,7 @@ describe('get runtime field', () => { } as unknown as DataViewLazy) ); - getRuntimeField({ + await getRuntimeField({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/put_runtime_field.test.ts b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/put_runtime_field.test.ts index a0ea59c8e663c..8b0a75d5aa970 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/put_runtime_field.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/put_runtime_field.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('put runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -27,7 +27,7 @@ describe('put runtime field', () => { } as unknown as DataViewLazy) ); - putRuntimeField({ + await putRuntimeField({ dataViewsService, counterName: 'PUT /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/update_runtime_field.test.ts b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/update_runtime_field.test.ts index 69154d89f3a1d..8e70b6ad4bf6e 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/update_runtime_field.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/runtime_fields/update_runtime_field.test.ts @@ -12,7 +12,7 @@ import { getUsageCollection } from '../test_utils'; import { DataViewLazy } from '../../../../common'; describe('update runtime field', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); dataViewsService.getDataViewLazy.mockImplementation( @@ -29,7 +29,7 @@ describe('update runtime field', () => { } as unknown as DataViewLazy) ); - updateRuntimeField({ + await updateRuntimeField({ dataViewsService, counterName: 'POST /path', usageCollection, diff --git a/src/plugins/data_views/server/rest_api_routes/public/update_data_view.test.ts b/src/plugins/data_views/server/rest_api_routes/public/update_data_view.test.ts index 4f4056a6d597a..64ab989457f4c 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/update_data_view.test.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/update_data_view.test.ts @@ -11,9 +11,9 @@ import { dataViewsService } from '../../mocks'; import { getUsageCollection } from './test_utils'; describe('get default data view', () => { - it('call usageCollection', () => { + it('call usageCollection', async () => { const usageCollection = getUsageCollection(); - updateDataView({ + await updateDataView({ dataViewsService, counterName: 'GET /path', usageCollection, diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 0af9e4ab303a4..9353df2c63a2d 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import type { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; import { type DataView, DataViewType } from '@kbn/data-views-plugin/public'; import { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; -import { ENABLE_ESQL } from '@kbn/discover-utils'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { TextBasedLanguages } from '@kbn/esql-utils'; import { useSavedSearch, diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 902cc0a38e717..3563697138cc4 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -29,6 +29,7 @@ import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; @@ -44,7 +45,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { UnifiedDocViewerStart } from '@kbn/unified-doc-viewer-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import { TRUNCATE_MAX_HEIGHT, ENABLE_ESQL } from '@kbn/discover-utils'; +import { TRUNCATE_MAX_HEIGHT } from '@kbn/discover-utils'; import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; import type { ObservabilityAIAssistantPublicSetup, diff --git a/src/plugins/discover/server/locator/title_from_locator.test.ts b/src/plugins/discover/server/locator/title_from_locator.test.ts index f3e459a8bf39b..abb0636c32a1f 100644 --- a/src/plugins/discover/server/locator/title_from_locator.test.ts +++ b/src/plugins/discover/server/locator/title_from_locator.test.ts @@ -105,7 +105,7 @@ test(`throws error if DiscoverAppLocatorParams do not contain a saved search ID` return await provider(mockPayload[0].params); }; - expect(testFn).rejects.toEqual( + await expect(testFn).rejects.toEqual( new Error('DiscoverAppLocatorParams must contain a saved search reference') ); }); diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index f09d96f150a9b..fab0d56c0d828 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -30,7 +30,6 @@ import { TRUNCATE_MAX_HEIGHT, SHOW_FIELD_STATISTICS, ROW_HEIGHT_OPTION, - ENABLE_ESQL, } from '@kbn/discover-utils'; import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../common/constants'; @@ -310,25 +309,4 @@ export const getUiSettings: ( schema: schema.number({ min: 0 }), requiresPageReload: true, }, - [ENABLE_ESQL]: { - name: i18n.translate('discover.advancedSettings.enableESQLTitle', { - defaultMessage: 'Enable ES|QL', - }), - value: true, - description: i18n.translate('discover.advancedSettings.enableESQLDescription', { - defaultMessage: - 'This setting enables ES|QL in Kibana. By switching it off you will hide the ES|QL user interface from various applications. However, users will be able to access existing ES|QL saved searches, visualizations, etc. If you have feedback on this experience please reach out to us on {link}', - values: { - link: - `` + - i18n.translate('discover.advancedSettings.enableESQL.discussLinkText', { - defaultMessage: 'https://ela.st/esql-feedback', - }) + - '', - }, - }), - requiresPageReload: true, - category: ['discover'], - schema: schema.boolean(), - }, }); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 083fd9e774ce9..0a943736d0a75 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -62,7 +62,6 @@ export type { ChartActionContext, ContainerInput, ContainerOutput, - EmbeddableAppContext, EmbeddableContainerSettings, EmbeddableContext, EmbeddableEditorState, diff --git a/src/plugins/embeddable/public/lib/embeddables/compatibility/edit_legacy_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/compatibility/edit_legacy_embeddable.test.tsx index fdceeda6dd60f..11c7c6683f39c 100644 --- a/src/plugins/embeddable/public/lib/embeddables/compatibility/edit_legacy_embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/compatibility/edit_legacy_embeddable.test.tsx @@ -84,7 +84,7 @@ test('canEditEmbeddable returns false when edit url is available, in edit mode, test('getEditHref returns the edit url', async () => { const embeddable = new EditableEmbeddable({ id: '123', viewMode: ViewMode.EDIT }, true); - expect(embeddable.getEditHref()).toBe(embeddable.getOutput().editUrl); + expect(await embeddable.getEditHref()).toBe(embeddable.getOutput().editUrl); }); test('redirects to app using state transfer', async () => { diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 07c52f6cbeed1..fb6a093d54817 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -12,10 +12,10 @@ import * as Rx from 'rxjs'; import { merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, skip } from 'rxjs'; import { RenderCompleteDispatcher } from '@kbn/kibana-utils-plugin/public'; +import { EmbeddableAppContext } from '@kbn/presentation-publishing'; import { Adapters } from '../types'; import { IContainer } from '../containers'; import { - EmbeddableAppContext, EmbeddableError, EmbeddableOutput, IEmbeddable, @@ -189,7 +189,7 @@ export abstract class Embeddable< public isCompatibleWithUnifiedSearch: LegacyEmbeddableAPI['isCompatibleWithUnifiedSearch']; public savedObjectId: LegacyEmbeddableAPI['savedObjectId']; - public getEditHref(): string | undefined { + public async getEditHref(): Promise { return this.getOutput().editUrl ?? undefined; } diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 050e18c2442c5..2c4131abd824d 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -25,6 +25,7 @@ import { PublishesPhaseEvents, PublishesSavedObjectId, HasLegacyLibraryTransforms, + EmbeddableAppContext, } from '@kbn/presentation-publishing'; import { Observable } from 'rxjs'; import { EmbeddableInput } from '../../../common/types'; @@ -58,14 +59,6 @@ export type LegacyEmbeddableAPI = HasType & EmbeddableHasTimeRange & PublishesSavedObjectId; -export interface EmbeddableAppContext { - /** - * Current app's path including query and hash starting from {appId} - */ - getCurrentPath?: () => string; - currentAppId?: string; -} - export interface EmbeddableOutput { // Whether the embeddable is actively loading. loading?: boolean; diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index cf4637d46915c..e65f8b2f61001 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -19,10 +19,5 @@ export { EmbeddableRoot } from './embeddable_root'; export { ErrorEmbeddable } from './error_embeddable'; export { isErrorEmbeddable } from './is_error_embeddable'; export { isEmbeddable } from './is_embeddable'; -export type { - EmbeddableAppContext, - EmbeddableInput, - EmbeddableOutput, - IEmbeddable, -} from './i_embeddable'; +export type { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; diff --git a/src/plugins/embeddable/public/lib/state_transfer/types.ts b/src/plugins/embeddable/public/lib/state_transfer/types.ts index db6085f6ea276..cc9e2e0ee8ef6 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/types.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/types.ts @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -import { Optional } from '@kbn/utility-types'; -import { EmbeddableInput, SavedObjectEmbeddableInput } from '..'; - export const EMBEDDABLE_EDITOR_STATE_KEY = 'embeddable_editor_state'; /** @@ -19,7 +16,7 @@ export interface EmbeddableEditorState { originatingApp: string; originatingPath?: string; embeddableId?: string; - valueInput?: EmbeddableInput; + valueInput?: unknown; /** * Pass current search session id when navigating to an editor, @@ -40,7 +37,7 @@ export const EMBEDDABLE_PACKAGE_STATE_KEY = 'embeddable_package_state'; */ export interface EmbeddablePackageState { type: string; - input: Optional | Optional; + input: unknown; embeddableId?: string; size?: { width?: number; diff --git a/src/plugins/files/server/routes/file_kind/upload.test.ts b/src/plugins/files/server/routes/file_kind/upload.test.ts index 49a207ea80345..ae85a9e371c47 100644 --- a/src/plugins/files/server/routes/file_kind/upload.test.ts +++ b/src/plugins/files/server/routes/file_kind/upload.test.ts @@ -36,7 +36,7 @@ describe('upload', () => { beforeEach(async () => { ({ ctx, fileService } = createFileKindsRequestHandlerContextMock()); uploadContent = jest.fn(); - deleteFn = jest.fn(); + deleteFn = jest.fn(async () => {}); // We need it to be a promise, or it'll crash because of missing `.catch` fileService.getById.mockResolvedValueOnce({ id: 'test', data: { size: 1 }, diff --git a/src/plugins/files/server/routes/file_kind/upload.ts b/src/plugins/files/server/routes/file_kind/upload.ts index 9a97f685cc85d..74faaf9334fe9 100644 --- a/src/plugins/files/server/routes/file_kind/upload.ts +++ b/src/plugins/files/server/routes/file_kind/upload.ts @@ -76,7 +76,7 @@ export const handler: CreateHandler = async ({ files, fileKind }, req, logger.info( `File (id: ${file.id}) upload aborted. Deleting file due to self-destruct flag.` ); - file.delete(); // fire and forget + file.delete().catch(() => {}); // fire and forget } return res.customError({ body: { message: e.message }, statusCode: 499 }); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts index 6939b20f4169c..76f79ed50348a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts @@ -27,7 +27,7 @@ export function registerCloudProviderUsageCollector( const cloudDetector = new CloudDetector(); // determine the cloud service in the background - cloudDetector.detectCloudService(ac.signal); + cloudDetector.detectCloudService(ac.signal).catch(() => {}); const collector = usageCollection.makeUsageCollector({ type: 'cloud_provider', diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts index 82e321c93783d..67697cac87b6c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts @@ -8,7 +8,7 @@ const create = () => { const mock = { - detectCloudService: jest.fn(), + detectCloudService: jest.fn(async () => {}), // We need it to be a promise, or it'll crash because of missing `.catch` getCloudDetails: jest.fn(), }; 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 0ed695f0b1999..1cf68fdf92f36 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -539,7 +539,7 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'discover:enableESQL': { + enableESQL: { 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 86c25366d4ceb..8464207aaa1d6 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -34,7 +34,7 @@ export interface UsageStats { 'discover:searchFieldsFromSource': boolean; 'discover:showFieldStatistics': boolean; 'discover:showMultiFields': boolean; - 'discover:enableESQL': boolean; + enableESQL: boolean; 'discover:maxDocFieldsDisplayed': number; 'securitySolution:rulesTableRefresh': string; 'observability:enableInspectEsQueries': boolean; diff --git a/src/plugins/maps_ems/server/plugin.ts b/src/plugins/maps_ems/server/plugin.ts index ebdc6814fd6eb..255fbaf4f912a 100644 --- a/src/plugins/maps_ems/server/plugin.ts +++ b/src/plugins/maps_ems/server/plugin.ts @@ -38,7 +38,10 @@ export class MapsEmsPlugin implements Plugin { isEnterprisePlus = enterprise.state === 'valid'; } - plugins.licensing.refresh().then(updateLicenseState); + plugins.licensing + .refresh() + .then(updateLicenseState) + .catch(() => {}); plugins.licensing.license$.subscribe(updateLicenseState); } diff --git a/src/plugins/navigation/server/plugin.ts b/src/plugins/navigation/server/plugin.ts index e5d83b915b897..6724ed8c20c53 100644 --- a/src/plugins/navigation/server/plugin.ts +++ b/src/plugins/navigation/server/plugin.ts @@ -36,14 +36,12 @@ export class NavigationServerPlugin if (plugins.cloud?.isCloudEnabled && !this.isServerless()) { const config = this.initializerContext.config.get(); - core.getStartServices().then(([coreStart, deps]) => { - deps.cloudExperiments?.getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false).then((value) => { - if (value) { - core.uiSettings.registerGlobal(getUiSettings(config)); - } else { - this.removeUiSettings(coreStart, getUiSettings(config)); - } - }); + void core.getStartServices().then(async ([coreStart, deps]) => { + if (await deps.cloudExperiments?.getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false)) { + core.uiSettings.registerGlobal(getUiSettings(config)); + } else { + await this.removeUiSettings(coreStart, getUiSettings(config)); + } }); } diff --git a/src/plugins/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts b/src/plugins/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts index a8d0f12ab4bec..bf19e73d829fd 100644 --- a/src/plugins/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts +++ b/src/plugins/presentation_panel/public/panel_actions/edit_panel_action/edit_panel_action.ts @@ -74,7 +74,7 @@ export class EditPanelAction public async getHref({ embeddable }: EmbeddableApiContext): Promise { if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - return embeddable?.getEditHref?.(); + return await embeddable?.getEditHref?.(); } public async isCompatible({ embeddable }: EmbeddableApiContext) { @@ -84,6 +84,6 @@ export class EditPanelAction public async execute({ embeddable }: EmbeddableApiContext) { if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - embeddable.onEdit(); + await embeddable.onEdit(); } } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 8773c3a99f9d2..f5eb21f859bca 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10254,7 +10254,7 @@ "description": "Non-default value of setting." } }, - "discover:enableESQL": { + "enableESQL": { "type": "boolean", "_meta": { "description": "Non-default value of setting." diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts index ed5bb95ec5ef1..f2f143a7c61b5 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts @@ -63,6 +63,7 @@ export const DATA_DATASETS_INDEX_PATTERNS = [ { pattern: 'winlogbeat-*', patternName: 'winlogbeat', shipper: 'winlogbeat' }, { pattern: 'packetbeat-*', patternName: 'packetbeat', shipper: 'packetbeat' }, { pattern: 'filebeat-*', patternName: 'filebeat', shipper: 'filebeat' }, + { pattern: '.internal.alerts-*', patternName: 'alerts' }, // Security - 3rd party { pattern: '*apache*', patternName: 'apache' }, // Already in Observability (keeping it in here for documentation) { pattern: '*tomcat*', patternName: 'tomcat' }, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts index cc4bf29166488..20fbf231aa12a 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.test.ts @@ -71,6 +71,7 @@ describe('get_data_telemetry', () => { { name: 'filebeat-12314', docCount: 100, sizeInBytes: 10 }, { name: 'metricbeat-1234', docCount: 100, sizeInBytes: 10, isECS: false }, { name: '.app-search-1234', docCount: 0 }, + { name: '.internal.alerts-stack.alerts-default-0000001', sizeInBytes: 999 }, { name: 'logs-endpoint.1234', docCount: 0 }, // Matching pattern with a dot in the name { name: 'ml_host_risk_score_latest_default', docCount: 0 }, { name: 'ml_host_risk_score_latest', docCount: 0 }, // This should not match, @@ -165,6 +166,11 @@ describe('get_data_telemetry', () => { index_count: 1, doc_count: 0, }, + { + pattern_name: 'alerts', + index_count: 1, + size_in_bytes: 999, + }, { pattern_name: 'logs-endpoint', shipper: 'endpoint', diff --git a/src/plugins/text_based_languages/kibana.jsonc b/src/plugins/text_based_languages/kibana.jsonc index 545c1878cc4e4..7e9aba22067e8 100644 --- a/src/plugins/text_based_languages/kibana.jsonc +++ b/src/plugins/text_based_languages/kibana.jsonc @@ -4,7 +4,7 @@ "owner": "@elastic/kibana-esql", "plugin": { "id": "textBasedLanguages", - "server": false, + "server": true, "browser": true, "optionalPlugins": [ "indexManagement" diff --git a/src/plugins/text_based_languages/server/index.ts b/src/plugins/text_based_languages/server/index.ts new file mode 100644 index 0000000000000..bca404b161bf8 --- /dev/null +++ b/src/plugins/text_based_languages/server/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 const plugin = async () => { + const { TextBasedLanguagesServerPlugin } = await import('./plugin'); + return new TextBasedLanguagesServerPlugin(); +}; diff --git a/src/plugins/text_based_languages/server/plugin.ts b/src/plugins/text_based_languages/server/plugin.ts new file mode 100644 index 0000000000000..95a341a467cc5 --- /dev/null +++ b/src/plugins/text_based_languages/server/plugin.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 type { CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; +import { getUiSettings } from './ui_settings'; + +export class TextBasedLanguagesServerPlugin implements Plugin { + public setup(core: CoreSetup) { + core.uiSettings.register(getUiSettings()); + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/src/plugins/text_based_languages/server/ui_settings.ts b/src/plugins/text_based_languages/server/ui_settings.ts new file mode 100644 index 0000000000000..32717b0d2cb8c --- /dev/null +++ b/src/plugins/text_based_languages/server/ui_settings.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { schema } from '@kbn/config-schema'; + +import type { UiSettingsParams } from '@kbn/core/server'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; + +export const getUiSettings: () => Record = () => ({ + [ENABLE_ESQL]: { + name: i18n.translate('textBasedLanguages.advancedSettings.enableESQLTitle', { + defaultMessage: 'Enable ES|QL', + }), + value: true, + description: i18n.translate('textBasedLanguages.advancedSettings.enableESQLDescription', { + defaultMessage: + 'This setting enables ES|QL in Kibana. By switching it off you will hide the ES|QL user interface from various applications. However, users will be able to access existing ES|QL saved searches, visualizations, etc. If you have feedback on this experience please reach out to us on {link}', + values: { + link: + `` + + i18n.translate('textBasedLanguages.advancedSettings.enableESQL.discussLinkText', { + defaultMessage: 'https://ela.st/esql-feedback', + }) + + '', + }, + }), + requiresPageReload: true, + schema: schema.boolean(), + }, +}); diff --git a/src/plugins/text_based_languages/tsconfig.json b/src/plugins/text_based_languages/tsconfig.json index 8d3fef08e23a9..2cb4eddcbf7a0 100644 --- a/src/plugins/text_based_languages/tsconfig.json +++ b/src/plugins/text_based_languages/tsconfig.json @@ -15,7 +15,10 @@ "@kbn/core", "@kbn/expressions-plugin", "@kbn/data-views-plugin", - "@kbn/index-management" + "@kbn/index-management", + "@kbn/i18n", + "@kbn/config-schema", + "@kbn/esql-utils" ], "exclude": [ "target/**/*", diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx index 8cc220e77c8bc..5016b7c301bb4 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx @@ -46,7 +46,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => { setOriginatingPath(pathValue); setOriginatingApp(value); - setValueInput(valueInputValue); + setValueInput(valueInputValue as any); setEmbeddableId(embeddableIdValue); if (!valueInputValue) { diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx index 70c0c111ce581..5bd36f7097c76 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx @@ -52,7 +52,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => { } else { data.search.session.start(); } - setEmbeddableInput(valueInputValue); + setEmbeddableInput(valueInputValue as any); setEmbeddableId(embeddableId); setOriginatingApp(value); setOriginatingPath(pathValue); diff --git a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts index 71da8fadea4af..010644b1d2029 100644 --- a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts +++ b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const dataGrid = getService('dataGrid'); - describe('dashboard embeddable data grid', () => { + // FLAKY: https://github.com/elastic/kibana/issues/181955 + describe.skip('dashboard embeddable data grid', () => { before(async () => { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); diff --git a/test/functional/apps/discover/group3/_request_counts.ts b/test/functional/apps/discover/group3/_request_counts.ts index 636f3343877fd..3503258cb6c0e 100644 --- a/test/functional/apps/discover/group3/_request_counts.ts +++ b/test/functional/apps/discover/group3/_request_counts.ts @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', 'bfetch:disable': true, - 'discover:enableESQL': true, + enableESQL: true, }); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); }); diff --git a/test/functional/apps/discover/group4/_esql_view.ts b/test/functional/apps/discover/group4/_esql_view.ts index 31f323500b119..57c924e44693a 100644 --- a/test/functional/apps/discover/group4/_esql_view.ts +++ b/test/functional/apps/discover/group4/_esql_view.ts @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', - 'discover:enableESQL': true, + enableESQL: true, }; describe('discover esql view', async function () { diff --git a/test/functional/apps/visualize/group5/_tsvb_time_series.ts b/test/functional/apps/visualize/group5/_tsvb_time_series.ts index dbddadbe4a746..fb0309f8cec70 100644 --- a/test/functional/apps/visualize/group5/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/group5/_tsvb_time_series.ts @@ -145,7 +145,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(actualCountMin).to.be('3 hours'); }); - describe('Dark mode', () => { + // FLAKY: https://github.com/elastic/kibana/issues/182136 + describe.skip('Dark mode', () => { before(async () => { await kibanaServer.uiSettings.update({ 'theme:darkMode': true, diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 1dd002b1ecf45..67ee470764690 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -271,11 +271,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.graph.savePolicy (alternatives)', 'xpack.ilm.ui.enabled (boolean)', 'xpack.index_management.ui.enabled (boolean)', - 'xpack.index_management.enableIndexActions (any)', - 'xpack.index_management.enableLegacyTemplates (any)', - 'xpack.index_management.enableIndexStats (any)', - 'xpack.index_management.editableIndexSettings (any)', - 'xpack.index_management.enableDataStreamsStorageColumn (any)', 'xpack.infra.sources.default.fields.message (array)', 'xpack.index_management.enableTogglingDataRetention (any)', // It's a boolean (any because schema.conditional) /** @@ -292,6 +287,12 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.infra.featureFlags.alertsAndRulesDropdownEnabled (any)', 'xpack.infra.featureFlags.profilingEnabled (boolean)', + 'xpack.index_management.enableIndexActions (any)', + 'xpack.index_management.enableLegacyTemplates (any)', + 'xpack.index_management.enableIndexStats (any)', + 'xpack.index_management.editableIndexSettings (any)', + 'xpack.index_management.enableDataStreamsStorageColumn (any)', + 'xpack.index_management.enableMappingsSourceFieldSection (any)', 'xpack.license_management.ui.enabled (boolean)', 'xpack.maps.preserveDrawingBuffer (boolean)', 'xpack.maps.showMapsInspectorAdapter (boolean)', diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts index c60fe499fdec5..0df546beecc4c 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts @@ -31,7 +31,13 @@ const fetchHistoricalSummaryParamsSchema = t.type({ groupBy: allOrAnyStringOrArray, revision: t.number, }), - t.partial({ remoteName: t.string }), + t.partial({ + remoteName: t.string, + range: t.type({ + from: t.string, + to: t.string, + }), + }), ]) ), }), diff --git a/x-pack/packages/kbn-slo-schema/src/schema/common.ts b/x-pack/packages/kbn-slo-schema/src/schema/common.ts index 155164dee593f..5346b9507e0b9 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/common.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/common.ts @@ -81,7 +81,10 @@ const groupSummarySchema = t.type({ noData: t.number, }); -const dateRangeSchema = t.type({ from: dateType, to: dateType }); +const dateRangeSchema = t.type({ + from: t.union([dateType, t.string]), + to: t.union([dateType, t.string]), +}); export { ALL_VALUE, diff --git a/x-pack/packages/ml/response_stream/server/stream_factory.test.ts b/x-pack/packages/ml/response_stream/server/stream_factory.test.ts index 4b75cf4e0826a..839e7bfdfb9de 100644 --- a/x-pack/packages/ml/response_stream/server/stream_factory.test.ts +++ b/x-pack/packages/ml/response_stream/server/stream_factory.test.ts @@ -93,7 +93,7 @@ describe('streamFactory', () => { // the browser on the client side will automatically take care of unzipping // without the need for additional custom code. it('should encode and receive a compressed string based stream', (done) => { - (async () => { + void (async () => { const { end, push, responseWithHeaders } = streamFactory( { 'accept-encoding': 'gzip', @@ -133,7 +133,7 @@ describe('streamFactory', () => { }); it('should encode and receive a compressed NDJSON based stream', (done) => { - (async () => { + void (async () => { const { DELIMITER, end, push, responseWithHeaders } = streamFactory( { 'accept-encoding': 'gzip', diff --git a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts index 12fc0a85ff7aa..cce0b811c5875 100644 --- a/x-pack/packages/security/plugin_types_common/src/authorization/role.ts +++ b/x-pack/packages/security/plugin_types_common/src/authorization/role.ts @@ -30,6 +30,7 @@ export interface RoleKibanaPrivilege { export interface Role { name: string; + description?: string; elasticsearch: { cluster: string[]; indices: RoleIndexPrivilege[]; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ac5b3206b722f..b01199b4e7667 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -580,15 +580,17 @@ export class ActionsPlugin implements Plugin { - scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager); - }); + this.eventLogService!.isEsContextReady() + .then(() => { + scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager); + }) + .catch(() => {}); if (this.actionsConfig.preconfiguredAlertHistoryEsIndex) { createAlertHistoryIndexTemplate({ client: core.elasticsearch.client.asInternalUser, logger: this.logger, - }); + }).catch(() => {}); } this.validateEnabledConnectorTypes(plugins); diff --git a/x-pack/plugins/actions/server/routes/connector/get/get.test.ts b/x-pack/plugins/actions/server/routes/connector/get/get.test.ts index e191a721a59a9..28293ae7947f2 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/get.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/get/get.test.ts @@ -149,7 +149,7 @@ describe('getConnectorRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/connector/get_all/get_all.test.ts b/x-pack/plugins/actions/server/routes/connector/get_all/get_all.test.ts index 223a2d56c0843..0ab3b57e238cf 100644 --- a/x-pack/plugins/actions/server/routes/connector/get_all/get_all.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/get_all/get_all.test.ts @@ -89,7 +89,7 @@ describe('getAllConnectorsRoute', () => { const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/connector/get_all_system/get_all_system.test.ts b/x-pack/plugins/actions/server/routes/connector/get_all_system/get_all_system.test.ts index 8130c8cd3a809..07221aacddde7 100644 --- a/x-pack/plugins/actions/server/routes/connector/get_all_system/get_all_system.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/get_all_system/get_all_system.test.ts @@ -125,7 +125,7 @@ describe('getAllConnectorsIncludingSystemRoute', () => { const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/connector/list_types/list_types.test.ts b/x-pack/plugins/actions/server/routes/connector/list_types/list_types.test.ts index 91419ac562324..e7370c7638a89 100644 --- a/x-pack/plugins/actions/server/routes/connector/list_types/list_types.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/list_types/list_types.test.ts @@ -241,7 +241,7 @@ describe('listTypesRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/connector/list_types_system/list_types_system.test.ts b/x-pack/plugins/actions/server/routes/connector/list_types_system/list_types_system.test.ts index af6e057cafc9c..07d2d3adcd4f3 100644 --- a/x-pack/plugins/actions/server/routes/connector/list_types_system/list_types_system.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/list_types_system/list_types_system.test.ts @@ -242,7 +242,7 @@ describe('listTypesWithSystemRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts index dd1317f57cd27..7d9c7fd1a5899 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -172,7 +172,7 @@ describe('createActionRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); }); test('validates body to prevent empty strings', async () => { diff --git a/x-pack/plugins/actions/server/routes/delete.test.ts b/x-pack/plugins/actions/server/routes/delete.test.ts index 160092eb8c101..b250e607738e6 100644 --- a/x-pack/plugins/actions/server/routes/delete.test.ts +++ b/x-pack/plugins/actions/server/routes/delete.test.ts @@ -104,7 +104,7 @@ describe('deleteActionRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/execute.test.ts index 3bcd2cf60811d..39319ff1dabf2 100644 --- a/x-pack/plugins/actions/server/routes/execute.test.ts +++ b/x-pack/plugins/actions/server/routes/execute.test.ts @@ -167,7 +167,7 @@ describe('executeActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/get_oauth_access_token.test.ts b/x-pack/plugins/actions/server/routes/get_oauth_access_token.test.ts index ae06068273ca3..c1b7dd26aff15 100644 --- a/x-pack/plugins/actions/server/routes/get_oauth_access_token.test.ts +++ b/x-pack/plugins/actions/server/routes/get_oauth_access_token.test.ts @@ -216,7 +216,7 @@ describe('getOAuthAccessToken', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/create.test.ts b/x-pack/plugins/actions/server/routes/legacy/create.test.ts index e711f73265f53..05993e44746f9 100644 --- a/x-pack/plugins/actions/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/create.test.ts @@ -141,7 +141,7 @@ describe('createActionRoute', () => { const [context, req, res] = mockHandlerArguments({ actionsClient }, {}); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); }); it('should track every call', async () => { diff --git a/x-pack/plugins/actions/server/routes/legacy/delete.test.ts b/x-pack/plugins/actions/server/routes/legacy/delete.test.ts index e16614a57fd04..2bfb5c7810e46 100644 --- a/x-pack/plugins/actions/server/routes/legacy/delete.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/delete.test.ts @@ -112,7 +112,7 @@ describe('deleteActionRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/execute.test.ts b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts index 53e7d038dfea5..c989731407650 100644 --- a/x-pack/plugins/actions/server/routes/legacy/execute.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/execute.test.ts @@ -170,7 +170,7 @@ describe('executeActionRoute', () => { const [, handler] = router.post.mock.calls[0]; - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/get.test.ts b/x-pack/plugins/actions/server/routes/legacy/get.test.ts index 6a19400da0fb5..732c964fb8284 100644 --- a/x-pack/plugins/actions/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/get.test.ts @@ -146,7 +146,7 @@ describe('getActionRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts b/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts index e999c769f3dbb..e8657e56259e1 100644 --- a/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/get_all.test.ts @@ -97,7 +97,7 @@ describe('getAllActionRoute', () => { const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts b/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts index 4674bbb9936a6..ec57c4b9a99a9 100644 --- a/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/list_action_types.test.ts @@ -159,7 +159,7 @@ describe('listActionTypesRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/legacy/update.test.ts b/x-pack/plugins/actions/server/routes/legacy/update.test.ts index 304b504636c91..493d1c873690e 100644 --- a/x-pack/plugins/actions/server/routes/legacy/update.test.ts +++ b/x-pack/plugins/actions/server/routes/legacy/update.test.ts @@ -172,7 +172,7 @@ describe('updateActionRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts index aef179264ac48..9fdac7740129d 100644 --- a/x-pack/plugins/actions/server/routes/update.test.ts +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -174,7 +174,7 @@ describe('updateActionRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); }); diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index 85e06f0422431..1496974581e89 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -32,7 +32,9 @@ export function initializeActionsTelemetry( } export function scheduleActionsTelemetry(logger: Logger, taskManager: TaskManagerStartContract) { - scheduleTasks(logger, taskManager); + scheduleTasks(logger, taskManager).catch(() => { + // it shouldn't throw anything. But adding the catch just in case + }); } function registerActionsTelemetryTask( diff --git a/x-pack/plugins/aiops/server/plugin.ts b/x-pack/plugins/aiops/server/plugin.ts index 81ad429c387e0..7e99a89dab5b5 100755 --- a/x-pack/plugins/aiops/server/plugin.ts +++ b/x-pack/plugins/aiops/server/plugin.ts @@ -62,7 +62,7 @@ export class AiopsPlugin const router = core.http.createRouter(); // Register server side APIs - core.getStartServices().then(([coreStart, depsStart]) => { + void core.getStartServices().then(([coreStart, depsStart]) => { defineLogRateAnalysisRoute(router, aiopsLicense, this.logger, coreStart, this.usageCounter); defineCategorizationFieldValidationRoute(router, aiopsLicense, this.usageCounter); }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts index 5266858e6d3bc..3c7a1f4ff5478 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts @@ -221,7 +221,7 @@ export const groupingHandlerFactory = } }, MAX_CONCURRENT_QUERIES); - groupHistogramQueue.push(significantItemGroups); + await groupHistogramQueue.push(significantItemGroups); await groupHistogramQueue.drain(); } } catch (e) { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts index 1a61edde6864b..8fd134df69cac 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts @@ -156,7 +156,7 @@ export const histogramHandlerFactory = } }, MAX_CONCURRENT_QUERIES); - fieldValueHistogramQueue.push(significantTerms); + await fieldValueHistogramQueue.push(significantTerms); await fieldValueHistogramQueue.drain(); } diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts index 69b9cc923799d..0864efbcf63da 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts @@ -133,7 +133,7 @@ export function routeHandlerFactory( } // Do not call this using `await` so it will run asynchronously while we return the stream already. - runAnalysis(); + void runAnalysis(); return response.ok(responseWithHeaders); }); diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/install_with_timeout.ts b/x-pack/plugins/alerting/server/alerts_service/lib/install_with_timeout.ts index 759aeba71cd41..e93dcf2e09f34 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/install_with_timeout.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/install_with_timeout.ts @@ -48,10 +48,12 @@ export const installWithTimeout = async ({ reject(new Error(msg)); }, timeoutMs); - firstValueFrom(pluginStop$).then(() => { - clearTimeout(timeoutId); - reject(new InstallShutdownError()); - }); + firstValueFrom(pluginStop$) + .then(() => { + clearTimeout(timeoutId); + reject(new InstallShutdownError()); + }) + .catch(() => reject(new InstallShutdownError())); }); }; diff --git a/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.test.ts b/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.test.ts index 89ab626bed462..4a2ac84515fe9 100644 --- a/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.test.ts +++ b/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.test.ts @@ -121,7 +121,7 @@ describe('deleteBackfill()', () => { let rulesClient: RulesClient; beforeEach(async () => { - jest.resetAllMocks(); + jest.clearAllMocks(); rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValue(mockAdHocRunSO); unsecuredSavedObjectsClient.delete.mockResolvedValue({}); diff --git a/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.ts b/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.ts index fe43e3555e8d3..3851efc1c2ebd 100644 --- a/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.ts +++ b/x-pack/plugins/alerting/server/application/backfill/methods/delete/delete_backfill.ts @@ -85,7 +85,7 @@ async function deleteWithOCC(context: RulesClientContext, { id }: { id: string } ); // remove the associated task - context.taskManager.removeIfExists(id); + context.taskManager.removeIfExists(id).catch(() => {}); return removeResult; } catch (err) { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index a2f1c0dfa92f1..9ace9c2d8f491 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -3062,7 +3062,7 @@ describe('create()', () => { rulesClientParams.createAPIKey.mockImplementation(() => { throw new Error('no'); }); - expect( + await expect( async () => await rulesClient.create({ data }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Error creating rule: could not create API key - no"` diff --git a/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts index d9bc9c8816845..748b41c0d2f1a 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts @@ -1618,7 +1618,7 @@ describe('update()', () => { rulesClientParams.createAPIKey.mockImplementation(() => { throw new Error('no'); }); - expect( + await expect( async () => await rulesClient.update({ id: '1', diff --git a/x-pack/plugins/alerting/server/invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation.ts b/x-pack/plugins/alerting/server/invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation.ts index 6711dd3fd6d24..bde9cd8aa2131 100644 --- a/x-pack/plugins/alerting/server/invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation.ts +++ b/x-pack/plugins/alerting/server/invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation.ts @@ -14,7 +14,7 @@ export const bulkMarkApiKeysForInvalidation = async ( logger: Logger, savedObjectsClient: SavedObjectsClientContract ): Promise => { - withSpan({ name: 'bulkMarkApiKeysForInvalidation', type: 'rules' }, async () => { + await withSpan({ name: 'bulkMarkApiKeysForInvalidation', type: 'rules' }, async () => { if (apiKeys.length === 0) { return; } diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 907c85ab27da5..f0006e6974e33 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -617,12 +617,16 @@ export class AlertingPlugin { : Promise.resolve([]); }); - this.eventLogService!.isEsContextReady().then(() => { - scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); - }); - - scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager); - scheduleApiKeyInvalidatorTask(this.telemetryLogger, this.config, plugins.taskManager); + this.eventLogService!.isEsContextReady() + .then(() => { + scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); + }) + .catch(() => {}); // it shouldn't reject, but just in case + + scheduleAlertingHealthCheck(this.logger, this.config, plugins.taskManager).catch(() => {}); // it shouldn't reject, but just in case + scheduleApiKeyInvalidatorTask(this.telemetryLogger, this.config, plugins.taskManager).catch( + () => {} + ); // it shouldn't reject, but just in case return { listTypes: ruleTypeRegistry!.list.bind(this.ruleTypeRegistry!), diff --git a/x-pack/plugins/alerting/server/routes/backfill/apis/delete/delete_backfill_route.test.ts b/x-pack/plugins/alerting/server/routes/backfill/apis/delete/delete_backfill_route.test.ts index 55a8ea2ae22ea..5ba4b736fa81e 100644 --- a/x-pack/plugins/alerting/server/routes/backfill/apis/delete/delete_backfill_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/backfill/apis/delete/delete_backfill_route.test.ts @@ -65,6 +65,6 @@ describe('deleteBackfillRoute', () => { }); const [, handler] = router.delete.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: 'abc' } }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/backfill/apis/find/find_backfill_route.test.ts b/x-pack/plugins/alerting/server/routes/backfill/apis/find/find_backfill_route.test.ts index fb461fa416f9b..a8a0a210c8817 100644 --- a/x-pack/plugins/alerting/server/routes/backfill/apis/find/find_backfill_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/backfill/apis/find/find_backfill_route.test.ts @@ -140,6 +140,6 @@ describe('findBackfillRoute', () => { }); const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ rulesClient }, { body: mockFindOptions }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/backfill/apis/get/get_backfill_route.test.ts b/x-pack/plugins/alerting/server/routes/backfill/apis/get/get_backfill_route.test.ts index f24018b1c8deb..79ac8df1ed4b5 100644 --- a/x-pack/plugins/alerting/server/routes/backfill/apis/get/get_backfill_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/backfill/apis/get/get_backfill_route.test.ts @@ -99,6 +99,6 @@ describe('getBackfillRoute', () => { const [, handler] = router.get.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: { id: 'abc' } }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/backfill/apis/schedule/schedule_backfill_route.test.ts b/x-pack/plugins/alerting/server/routes/backfill/apis/schedule/schedule_backfill_route.test.ts index 30545a3ac1617..c08cca6dfe683 100644 --- a/x-pack/plugins/alerting/server/routes/backfill/apis/schedule/schedule_backfill_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/backfill/apis/schedule/schedule_backfill_route.test.ts @@ -148,6 +148,6 @@ describe('scheduleBackfillRoute', () => { { rulesClient }, { body: mockScheduleOptions } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/delete_rule.test.ts b/x-pack/plugins/alerting/server/routes/delete_rule.test.ts index b64dff27c5b90..71222d859d3c8 100644 --- a/x-pack/plugins/alerting/server/routes/delete_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/delete_rule.test.ts @@ -102,7 +102,7 @@ describe('deleteRuleRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/routes/get_action_error_log.test.ts index 408ed049044c6..f95fe38a5ee64 100644 --- a/x-pack/plugins/alerting/server/routes/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_action_error_log.test.ts @@ -115,7 +115,7 @@ describe('getActionErrorLogRoute', () => { ['notFound'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( `[Error: Saved object [alert/1] not found]` ); }); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts index 5eca11c27a4b2..319de59a01bfd 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts @@ -114,7 +114,7 @@ describe('getRuleAlertSummaryRoute', () => { ['notFound'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( `[Error: Saved object [alert/1] not found]` ); }); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts index e92b54769d329..af64b80851695 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_execution_kpi.test.ts @@ -99,7 +99,7 @@ describe('getRuleExecutionKPIRoute', () => { ['notFound'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( `[Error: Saved object [alert/1] not found]` ); }); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_execution_log.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_execution_log.test.ts index a0dd57da558eb..3b1f950fb45c6 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_execution_log.test.ts @@ -145,7 +145,7 @@ describe('getRuleExecutionLogRoute', () => { ['notFound'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot( `[Error: Saved object [alert/1] not found]` ); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts index e090cac73a932..d50d7fcb79e38 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -432,7 +432,7 @@ describe('createAlertRoute', () => { const [context, req, res] = mockHandlerArguments({ rulesClient }, {}); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts b/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts index fee6b3db736fe..9a3ccdf84c82c 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/delete.test.ts @@ -108,7 +108,7 @@ describe('deleteAlertRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/find.test.ts b/x-pack/plugins/alerting/server/routes/legacy/find.test.ts index 60ca90865be1d..013fa119dbdf1 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/find.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/find.test.ts @@ -149,7 +149,7 @@ describe('findAlertRoute', () => { }, ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts index 7d1a6ac656896..e97996837f1d7 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts @@ -151,7 +151,7 @@ describe('getAlertRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts index c680007f197e1..b9abeafd00ecc 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts @@ -219,7 +219,7 @@ describe('listAlertTypesRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts index 9e23470215d36..44a4cf629813a 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/update.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/update.test.ts @@ -225,7 +225,7 @@ describe('updateAlertRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/archive/archive_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/archive/archive_maintenance_window_route.test.ts index 040ba6e8ea9ab..dfd5073ae1026 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/archive/archive_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/archive/archive_maintenance_window_route.test.ts @@ -112,7 +112,7 @@ describe('archiveMaintenanceWindowRoute', () => { }, } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -127,6 +127,6 @@ describe('archiveMaintenanceWindowRoute', () => { const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/bulk_get/bulk_get_maintenance_windows_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/bulk_get/bulk_get_maintenance_windows_route.test.ts index c39fe061b69e5..eaab39665fb6b 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/bulk_get/bulk_get_maintenance_windows_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/bulk_get/bulk_get_maintenance_windows_route.test.ts @@ -110,7 +110,7 @@ describe('bulkGetMaintenanceWindowRoute', () => { { maintenanceWindowClient }, { body: { ids: ['test-id-1', 'test-id-2', 'test-id-3'] } } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -124,6 +124,6 @@ describe('bulkGetMaintenanceWindowRoute', () => { }); const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/create_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/create_maintenance_window_route.test.ts index 5437533ba8342..9cac558652d76 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/create_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/create/create_maintenance_window_route.test.ts @@ -101,7 +101,7 @@ describe('createMaintenanceWindowRoute', () => { { maintenanceWindowClient }, { body: createParams } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -116,6 +116,6 @@ describe('createMaintenanceWindowRoute', () => { const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/delete/delete_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/delete/delete_maintenance_window_route.test.ts index 7ac0db328cf9d..b248fe1394318 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/delete/delete_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/delete/delete_maintenance_window_route.test.ts @@ -85,7 +85,7 @@ describe('deleteMaintenanceWindowRoute', () => { { maintenanceWindowClient }, { params: { id: 'test-id' } } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -100,6 +100,6 @@ describe('deleteMaintenanceWindowRoute', () => { const [, handler] = router.delete.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts index a9a7d22e20751..21d821d2e0db3 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/find/find_maintenance_windows_route.test.ts @@ -93,7 +93,7 @@ describe('findMaintenanceWindowsRoute', () => { }); const [, handler] = router.get.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -107,6 +107,6 @@ describe('findMaintenanceWindowsRoute', () => { }); const [, handler] = router.get.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/finish/finish_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/finish/finish_maintenance_window_route.test.ts index e7ec470839fe9..4141d114c98d8 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/finish/finish_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/finish/finish_maintenance_window_route.test.ts @@ -88,7 +88,7 @@ describe('finishMaintenanceWindowRoute', () => { { maintenanceWindowClient }, { params: { id: 'test-id' } } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -102,6 +102,6 @@ describe('finishMaintenanceWindowRoute', () => { }); const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get/get_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get/get_maintenance_window_route.test.ts index 9e17aed88eaf0..487878fdb0e9a 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get/get_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get/get_maintenance_window_route.test.ts @@ -88,7 +88,7 @@ describe('getMaintenanceWindowRoute', () => { { maintenanceWindowClient }, { params: { id: 'test-id' } } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -102,6 +102,6 @@ describe('getMaintenanceWindowRoute', () => { }); const [, handler] = router.get.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get_active/get_active_maintenance_windows_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get_active/get_active_maintenance_windows_route.test.ts index c9f9fa2841374..4121aeb1d7f11 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get_active/get_active_maintenance_windows_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/get_active/get_active_maintenance_windows_route.test.ts @@ -92,7 +92,7 @@ describe('getActiveMaintenanceWindowsRoute', () => { }); const [, handler] = router.get.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/update_maintenance_window_route.test.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/update_maintenance_window_route.test.ts index d7425b150d1ea..a4bc9701f99a1 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/update_maintenance_window_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/apis/update/update_maintenance_window_route.test.ts @@ -115,7 +115,7 @@ describe('updateMaintenanceWindowRoute', () => { body: updateParams, } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); test('ensures only platinum license can access API', async () => { @@ -129,6 +129,6 @@ describe('updateMaintenanceWindowRoute', () => { }); const [, handler] = router.post.mock.calls[0]; const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} }); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts index 51a7da620dfb3..77221de2d714b 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/aggregate_rules_route.test.ts @@ -316,7 +316,7 @@ describe('aggregateRulesRoute', () => { }, ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_delete/bulk_delete_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_delete/bulk_delete_rules_route.test.ts index 3f2a31ac6cc96..90a13ba09a4cf 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_delete/bulk_delete_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_delete/bulk_delete_rules_route.test.ts @@ -100,7 +100,7 @@ describe('bulkDeleteRulesRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); it('ensures the rule type gets validated for the license', async () => { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_disable/bulk_disable_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_disable/bulk_disable_rules_route.test.ts index cd71a78a5f51c..0dc0793118c23 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_disable/bulk_disable_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_disable/bulk_disable_rules_route.test.ts @@ -101,7 +101,7 @@ describe('bulkDisableRulesRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); it('ensures the rule type gets validated for the license', async () => { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts index 82480acf779f9..295299db3a117 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_edit/bulk_edit_rules_route.test.ts @@ -169,7 +169,7 @@ describe('bulkEditRulesRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); it('ensures the rule type gets validated for the license', async () => { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_enable/bulk_enable_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_enable/bulk_enable_rules_route.test.ts index 9a0d22dadc9a4..272d91997a9f5 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/bulk_enable/bulk_enable_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/bulk_enable/bulk_enable_rules_route.test.ts @@ -103,7 +103,7 @@ describe('bulkEnableRulesRoute', () => { } ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); }); it('ensures the rule type gets validated for the license', async () => { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/clone/clone_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/clone/clone_rule_route.test.ts index 4ae904261eb24..13c6a69087635 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/clone/clone_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/clone/clone_rule_route.test.ts @@ -198,7 +198,7 @@ describe('cloneRuleRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts index 8f488dafd2fa5..ba6c3c25a1c43 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/create_rule_route.test.ts @@ -632,7 +632,7 @@ describe('createRuleRoute', () => { const [context, req, res] = mockHandlerArguments({ rulesClient }, {}); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/find/find_rules_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/find/find_rules_route.test.ts index 53c339a66b864..4e3c9b635ec3a 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/find/find_rules_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/find/find_rules_route.test.ts @@ -360,7 +360,7 @@ describe('findRulesRoute', () => { }, ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/get/get_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/get/get_rule_route.test.ts index f854109f16f1b..a9ae97e521ef5 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/get/get_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/get/get_rule_route.test.ts @@ -188,7 +188,7 @@ describe('getRuleRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts index bd6c8e144e91c..7b7d871007060 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts @@ -197,7 +197,7 @@ describe('resolveRuleRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/tags/get_rule_tags.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/tags/get_rule_tags.test.ts index a8a1992480841..02b9fd4e32a5f 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/tags/get_rule_tags.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/tags/get_rule_tags.test.ts @@ -118,7 +118,7 @@ describe('getRuleTagsRoute', () => { }, ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/update/update_rule_route.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/update/update_rule_route.test.ts index c04c1aa345d1a..e1ed7b52ae710 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/update/update_rule_route.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/update/update_rule_route.test.ts @@ -273,7 +273,7 @@ describe('updateRuleRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/routes/rule_types.test.ts b/x-pack/plugins/alerting/server/routes/rule_types.test.ts index f14c3472dcc93..a6483f15f9f1c 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.test.ts @@ -250,7 +250,7 @@ describe('ruleTypesRoute', () => { ['ok'] ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); }); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts index 5a969e7e8c836..ac58ab1a12685 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts @@ -148,7 +148,7 @@ describe('addGeneratedActionValues()', () => { }); test('throws error if KQL is not valid', async () => { - expect(async () => + await expect(async () => addGeneratedActionValues( [ { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index 8df9883247683..532511fb313f5 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -507,9 +507,9 @@ describe('getActionErrorLog()', () => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getRuleSavedObject()); eventLogClient.findEventsBySavedObjectIds.mockRejectedValueOnce(new Error('OMG 3!')); - expect(rulesClient.getActionErrorLog(getActionErrorLogParams())).rejects.toMatchInlineSnapshot( - `[Error: OMG 3!]` - ); + await expect( + rulesClient.getActionErrorLog(getActionErrorLogParams()) + ).rejects.toMatchInlineSnapshot(`[Error: OMG 3!]`); }); describe('authorization', () => { beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index b5815b6022cb9..77e3d19a37540 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -372,7 +372,7 @@ describe('getAlertSummary()', () => { eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(AlertSummaryFindEventsResult); const dateStart = 'ain"t no way this will get parsed as a date'; - expect(rulesClient.getAlertSummary({ id: '1', dateStart })).rejects.toMatchInlineSnapshot( + await expect(rulesClient.getAlertSummary({ id: '1', dateStart })).rejects.toMatchInlineSnapshot( `[Error: Invalid date for parameter dateStart: "ain"t no way this will get parsed as a date"]` ); }); @@ -381,7 +381,9 @@ describe('getAlertSummary()', () => { unsecuredSavedObjectsClient.get.mockRejectedValueOnce(new Error('OMG!')); eventLogClient.findEventsBySavedObjectIds.mockResolvedValueOnce(AlertSummaryFindEventsResult); - expect(rulesClient.getAlertSummary({ id: '1' })).rejects.toMatchInlineSnapshot(`[Error: OMG!]`); + await expect(rulesClient.getAlertSummary({ id: '1' })).rejects.toMatchInlineSnapshot( + `[Error: OMG!]` + ); }); test('findEvents throws an error', async () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 6bc7076ab3329..f8416e9cdb6b6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -553,7 +553,7 @@ describe('getExecutionLogForRule()', () => { eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); const dateStart = 'ain"t no way this will get parsed as a date'; - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams({ dateStart })) ).rejects.toMatchInlineSnapshot( `[Error: Invalid date for parameter dateStart: "ain"t no way this will get parsed as a date"]` @@ -565,7 +565,7 @@ describe('getExecutionLogForRule()', () => { eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); const dateEnd = 'ain"t no way this will get parsed as a date'; - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams({ dateEnd })) ).rejects.toMatchInlineSnapshot( `[Error: Invalid date for parameter dateEnd: "ain"t no way this will get parsed as a date"]` @@ -576,7 +576,7 @@ describe('getExecutionLogForRule()', () => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getRuleSavedObject()); eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams({ page: -3 })) ).rejects.toMatchInlineSnapshot(`[Error: Invalid page field "-3" - must be greater than 0]`); }); @@ -585,7 +585,7 @@ describe('getExecutionLogForRule()', () => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getRuleSavedObject()); eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams({ perPage: -3 })) ).rejects.toMatchInlineSnapshot(`[Error: Invalid perPage field "-3" - must be greater than 0]`); }); @@ -594,7 +594,7 @@ describe('getExecutionLogForRule()', () => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getRuleSavedObject()); eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); - expect( + await expect( rulesClient.getExecutionLogForRule( getExecutionLogByIdParams({ sort: [{ foo: { order: 'desc' } }] }) ) @@ -607,7 +607,7 @@ describe('getExecutionLogForRule()', () => { unsecuredSavedObjectsClient.get.mockRejectedValueOnce(new Error('OMG!')); eventLogClient.aggregateEventsBySavedObjectIds.mockResolvedValueOnce(aggregateResults); - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams()) ).rejects.toMatchInlineSnapshot(`[Error: OMG!]`); }); @@ -616,7 +616,7 @@ describe('getExecutionLogForRule()', () => { unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getRuleSavedObject()); eventLogClient.aggregateEventsBySavedObjectIds.mockRejectedValueOnce(new Error('OMG 2!')); - expect( + await expect( rulesClient.getExecutionLogForRule(getExecutionLogByIdParams()) ).rejects.toMatchInlineSnapshot(`[Error: OMG 2!]`); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index 0f72a1e20d1d5..dbf80eb978d2f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -287,7 +287,7 @@ describe('updateApiKey()', () => { rulesClientParams.createAPIKey.mockImplementation(() => { throw new Error('no'); }); - expect( + await expect( async () => await rulesClient.updateApiKey({ id: '1' }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Error updating API key for rule: could not create API key - no"` diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 627f88bab4e69..713ee52015239 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -551,7 +551,7 @@ export class TaskRunner< // Most likely a 409 conflict error, which is ok, we'll try again at the next rule run this.logger.debug(`Failed to clear expired snoozes: ${e.message}`); } - })(); + })().catch(() => {}); return runRuleParams; }); diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 507970d436d6f..0cc08db911226 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -38,7 +38,7 @@ export function initializeAlertingTelemetry( export function scheduleAlertingTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { if (taskManager) { - scheduleTasks(logger, taskManager); + scheduleTasks(logger, taskManager).catch(() => {}); // it shouldn't reject, but just in case } } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index 9e468d1702e92..f1e0387c3a5d6 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -6,7 +6,6 @@ */ import { CoreStart } from '@kbn/core/public'; -import type { EmbeddableAppContext } from '@kbn/embeddable-plugin/public'; import { EmbeddableFactory, EmbeddableFactoryNotFoundError, @@ -17,6 +16,7 @@ import { ReactEmbeddableRenderer, } from '@kbn/embeddable-plugin/public'; import { PresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableAppContext } from '@kbn/presentation-publishing'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import React, { FC } from 'react'; import ReactDOM from 'react-dom'; diff --git a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts index 0a4e66917814d..5bb3e5ed6050c 100644 --- a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts +++ b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts @@ -40,7 +40,7 @@ export const useIncomingEmbeddable = (selectedPage: CanvasPage) => { useEffect(() => { if (isByValueEnabled && incomingEmbeddable) { - const { embeddableId, input: incomingInput, type } = incomingEmbeddable; + const { embeddableId, input: incomingInput, type } = incomingEmbeddable as any; // retrieve existing element const originalElement = selectedPage.elements.find( diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index 9dd6dc1bdd64f..16e39eb46ee34 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -99,7 +99,7 @@ export class CanvasPlugin implements Plugin { public start(coreStart: CoreStart) { const client = coreStart.savedObjects.createInternalRepository(); - initializeTemplates(client); + initializeTemplates(client).catch(() => {}); } public stop() {} diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.ts index 7f5a414c5dba0..d476edb8ca4ac 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/zip.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.ts @@ -33,7 +33,7 @@ export function initializeZipShareableWorkpadRoute(deps: RouteInitializerDeps) { archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` }); const result = { headers: { 'content-type': 'application/zip' }, body: archive }; - archive.finalize(); + await archive.finalize(); return response.ok(result); } diff --git a/x-pack/plugins/cases/server/authorization/authorization.ts b/x-pack/plugins/cases/server/authorization/authorization.ts index e3dc970b38182..c67b69c4f305f 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.ts @@ -171,7 +171,7 @@ export class Authorization { return { authorized, unauthorized }; } - private async logSavedObjects({ + private logSavedObjects({ entities, operation, error, diff --git a/x-pack/plugins/cases/server/common/limiter_checker/limiters/persistable_state_and_external_references.test.ts b/x-pack/plugins/cases/server/common/limiter_checker/limiters/persistable_state_and_external_references.test.ts index df40e3841f1d7..d126237dd8aac 100644 --- a/x-pack/plugins/cases/server/common/limiter_checker/limiters/persistable_state_and_external_references.test.ts +++ b/x-pack/plugins/cases/server/common/limiter_checker/limiters/persistable_state_and_external_references.test.ts @@ -39,8 +39,8 @@ describe('PersistableStateAndExternalReferencesLimiter', () => { }); describe('countOfItemsWithinCase', () => { - it('calls the attachment service with the right params', () => { - limiter.countOfItemsWithinCase(caseId); + it('calls the attachment service with the right params', async () => { + await limiter.countOfItemsWithinCase(caseId); expect( attachmentService.countPersistableStateAndExternalReferenceAttachments diff --git a/x-pack/plugins/cases/server/telemetry/index.ts b/x-pack/plugins/cases/server/telemetry/index.ts index af1e9125a8a55..5f10dcc6a3c72 100644 --- a/x-pack/plugins/cases/server/telemetry/index.ts +++ b/x-pack/plugins/cases/server/telemetry/index.ts @@ -36,7 +36,7 @@ interface CreateCasesTelemetryArgs { kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; } -export const createCasesTelemetry = async ({ +export const createCasesTelemetry = ({ core, taskManager, usageCollection, diff --git a/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts index 0e51a343e3770..c0db69a5dc128 100644 --- a/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts +++ b/x-pack/plugins/cloud_defend/server/lib/fleet_util.ts @@ -89,9 +89,15 @@ async function addDataViewToAllSpaces(savedObjectsClient: SavedObjectsClientCont perPage: 100, }); - cloudDefendDataViews.saved_objects.forEach((dataView) => { - savedObjectsClient.updateObjectsSpaces([{ id: dataView.id, type: 'index-pattern' }], ['*'], []); - }); + await Promise.all( + cloudDefendDataViews.saved_objects.map((dataView) => + savedObjectsClient.updateObjectsSpaces( + [{ id: dataView.id, type: 'index-pattern' }], + ['*'], + [] + ) + ) + ); } export const getCloudDefendAgentPolicies = async ( diff --git a/x-pack/plugins/cloud_defend/server/plugin.ts b/x-pack/plugins/cloud_defend/server/plugin.ts index c8a6bc5083b1c..b7c5016776816 100644 --- a/x-pack/plugins/cloud_defend/server/plugin.ts +++ b/x-pack/plugins/cloud_defend/server/plugin.ts @@ -55,23 +55,26 @@ export class CloudDefendPlugin implements Plugin { - plugins.fleet.registerExternalCallback( - 'packagePolicyCreate', - async (packagePolicy: NewPackagePolicy): Promise => { - const license = await plugins.licensing.refresh(); - if (isCloudDefendPackage(packagePolicy.package?.name)) { - if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { - throw new Error( - 'To use this feature you must upgrade your subscription or start a trial' - ); + plugins.fleet + .fleetSetupCompleted() + .then(async () => { + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async (packagePolicy: NewPackagePolicy): Promise => { + const license = await plugins.licensing.refresh(); + if (isCloudDefendPackage(packagePolicy.package?.name)) { + if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { + throw new Error( + 'To use this feature you must upgrade your subscription or start a trial' + ); + } } - } - return packagePolicy; - } - ); - }); + return packagePolicy; + } + ); + }) + .catch(() => {}); // it shouldn't reject, but just in case plugins.fleet.registerExternalCallback( 'packagePolicyPostCreate', diff --git a/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts index 0e9cf02ad0663..a3d166cf69688 100644 --- a/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts @@ -29,9 +29,15 @@ async function addDataViewToAllSpaces(savedObjectsClient: SavedObjectsClientCont perPage: 100, }); - cspmDataViews.saved_objects.forEach((dataView) => { - savedObjectsClient.updateObjectsSpaces([{ id: dataView.id, type: 'index-pattern' }], ['*'], []); - }); + await Promise.all( + cspmDataViews.saved_objects.map((dataView) => + savedObjectsClient.updateObjectsSpaces( + [{ id: dataView.id, type: 'index-pattern' }], + ['*'], + [] + ) + ) + ); } export const isCspPackagePolicyInstalled = async ( diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.ts b/x-pack/plugins/cloud_security_posture/server/plugin.ts index 58a143b220857..561d1bcaa7efd 100755 --- a/x-pack/plugins/cloud_security_posture/server/plugin.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.ts @@ -100,102 +100,104 @@ export class CspPlugin } public start(core: CoreStart, plugins: CspServerPluginStartDeps): CspServerPluginStart { - plugins.fleet.fleetSetupCompleted().then(async () => { - const packageInfo = await plugins.fleet.packageService.asInternalUser.getInstallation( - CLOUD_SECURITY_POSTURE_PACKAGE_NAME - ); - - // If package is installed we want to make sure all needed assets are installed - if (packageInfo) { - // noinspection ES6MissingAwait - this.initialize(core, plugins.taskManager); - } - - plugins.fleet.registerExternalCallback( - 'packagePolicyCreate', - async (packagePolicy: NewPackagePolicy): Promise => { - const license = await plugins.licensing.refresh(); - if (isCspPackage(packagePolicy.package?.name)) { - if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { - throw new Error( - 'To use this feature you must upgrade your subscription or start a trial' - ); - } + plugins.fleet + .fleetSetupCompleted() + .then(async () => { + const packageInfo = await plugins.fleet.packageService.asInternalUser.getInstallation( + CLOUD_SECURITY_POSTURE_PACKAGE_NAME + ); + + // If package is installed we want to make sure all needed assets are installed + if (packageInfo) { + this.initialize(core, plugins.taskManager).catch(() => {}); + } - if (!isSingleEnabledInput(packagePolicy.inputs)) { - throw new Error('Only one enabled input is allowed per policy'); + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async (packagePolicy: NewPackagePolicy): Promise => { + const license = await plugins.licensing.refresh(); + if (isCspPackage(packagePolicy.package?.name)) { + if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { + throw new Error( + 'To use this feature you must upgrade your subscription or start a trial' + ); + } + + if (!isSingleEnabledInput(packagePolicy.inputs)) { + throw new Error('Only one enabled input is allowed per policy'); + } } - } - return packagePolicy; - } - ); - - plugins.fleet.registerExternalCallback( - 'packagePolicyCreate', - async ( - packagePolicy: NewPackagePolicy, - soClient: SavedObjectsClientContract - ): Promise => { - if (isCspPackage(packagePolicy.package?.name)) { - return cleanupCredentials(packagePolicy); + return packagePolicy; } + ); + + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async ( + packagePolicy: NewPackagePolicy, + soClient: SavedObjectsClientContract + ): Promise => { + if (isCspPackage(packagePolicy.package?.name)) { + return cleanupCredentials(packagePolicy); + } - return packagePolicy; - } - ); - - plugins.fleet.registerExternalCallback( - 'packagePolicyUpdate', - async ( - packagePolicy: UpdatePackagePolicy, - soClient: SavedObjectsClientContract - ): Promise => { - if (isCspPackage(packagePolicy.package?.name)) { - return cleanupCredentials(packagePolicy); + return packagePolicy; } - - return packagePolicy; - } - ); - - plugins.fleet.registerExternalCallback( - 'packagePolicyPostCreate', - async ( - packagePolicy: PackagePolicy, - soClient: SavedObjectsClientContract - ): Promise => { - if (isCspPackage(packagePolicy.package?.name)) { - await this.initialize(core, plugins.taskManager); - await onPackagePolicyPostCreateCallback(this.logger, packagePolicy, soClient); + ); + + plugins.fleet.registerExternalCallback( + 'packagePolicyUpdate', + async ( + packagePolicy: UpdatePackagePolicy, + soClient: SavedObjectsClientContract + ): Promise => { + if (isCspPackage(packagePolicy.package?.name)) { + return cleanupCredentials(packagePolicy); + } return packagePolicy; } + ); + + plugins.fleet.registerExternalCallback( + 'packagePolicyPostCreate', + async ( + packagePolicy: PackagePolicy, + soClient: SavedObjectsClientContract + ): Promise => { + if (isCspPackage(packagePolicy.package?.name)) { + await this.initialize(core, plugins.taskManager); + await onPackagePolicyPostCreateCallback(this.logger, packagePolicy, soClient); + + return packagePolicy; + } - return packagePolicy; - } - ); - - plugins.fleet.registerExternalCallback( - 'packagePolicyPostDelete', - async (deletedPackagePolicies: DeepReadonly) => { - for (const deletedPackagePolicy of deletedPackagePolicies) { - if (isCspPackage(deletedPackagePolicy.package?.name)) { - const soClient = core.savedObjects.createInternalRepository(); - const packagePolicyService = plugins.fleet.packagePolicyService; - const isPackageExists = await isCspPackagePolicyInstalled( - packagePolicyService, - soClient, - this.logger - ); - if (!isPackageExists) { - await this.uninstallResources(plugins.taskManager, this.logger); + return packagePolicy; + } + ); + + plugins.fleet.registerExternalCallback( + 'packagePolicyPostDelete', + async (deletedPackagePolicies: DeepReadonly) => { + for (const deletedPackagePolicy of deletedPackagePolicies) { + if (isCspPackage(deletedPackagePolicy.package?.name)) { + const soClient = core.savedObjects.createInternalRepository(); + const packagePolicyService = plugins.fleet.packagePolicyService; + const isPackageExists = await isCspPackagePolicyInstalled( + packagePolicyService, + soClient, + this.logger + ); + if (!isPackageExists) { + await this.uninstallResources(plugins.taskManager, this.logger); + } } } } - } - ); - }); + ); + }) + .catch(() => {}); // it shouldn't reject, but just in case return {}; } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts index 732017db1090f..7b46e4ef113c3 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts @@ -27,7 +27,7 @@ import { defineGetCspBenchmarkRulesStatesRoute } from './benchmark_rules/get_sta * 1. Registers routes * 2. Registers routes handler context */ -export async function setupRoutes({ +export function setupRoutes({ core, logger, isPluginInitialized, diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index 657468b76d615..bf5644a0f8bdd 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -57,7 +57,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin { + void firstValueFrom(this.config$).then((config) => { // remoteClusters.isUiEnabled is driven by the xpack.remote_clusters.ui.enabled setting. // The CCR UI depends upon the Remote Clusters UI (e.g. by cross-linking to it), so if // the Remote Clusters UI is disabled we can't show the CCR UI. diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss deleted file mode 100644 index a3682bfd7d96c..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_embedded_map.scss +++ /dev/null @@ -1,8 +0,0 @@ -.embeddedMap__content { - width: 100%; - height: 100%; - display: flex; - flex: 1 1 100%; - z-index: 1; - min-height: 0; // Absolute must for Firefox to scroll contents -} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss deleted file mode 100644 index 5b3c6b4990ff1..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'embedded_map'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx deleted file mode 100644 index 7b0838329c0ca..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/embedded_map.tsx +++ /dev/null @@ -1,148 +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, { useEffect, useRef, useState } from 'react'; - -import { htmlIdGenerator } from '@elastic/eui'; -import type { LayerDescriptor } from '@kbn/maps-plugin/common'; -import { INITIAL_LOCATION } from '@kbn/maps-plugin/common'; -import type { - MapEmbeddable, - MapEmbeddableInput, - MapEmbeddableOutput, -} from '@kbn/maps-plugin/public/embeddable'; -import type { RenderTooltipContentParams } from '@kbn/maps-plugin/public'; -import { MAP_SAVED_OBJECT_TYPE } from '@kbn/maps-plugin/public'; -import type { EmbeddableFactory, ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; -import { useDataVisualizerKibana } from '../../../kibana_context'; -import './_embedded_map.scss'; - -export function EmbeddedMapComponent({ - layerList, - mapEmbeddableInput, - renderTooltipContent, -}: { - layerList: LayerDescriptor[]; - mapEmbeddableInput?: MapEmbeddableInput; - renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element; -}) { - const [embeddable, setEmbeddable] = useState(); - - const embeddableRoot: React.RefObject = useRef(null); - const baseLayers = useRef(); - - const { - services: { embeddable: embeddablePlugin, maps: mapsPlugin, data }, - } = useDataVisualizerKibana(); - - const factory: - | EmbeddableFactory - | undefined = embeddablePlugin - ? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE) - : undefined; - - // Update the layer list with updated geo points upon refresh - useEffect(() => { - async function updateIndexPatternSearchLayer() { - if ( - embeddable && - !isErrorEmbeddable(embeddable) && - Array.isArray(layerList) && - Array.isArray(baseLayers.current) - ) { - embeddable.setLayerList([...baseLayers.current, ...layerList]); - } - } - updateIndexPatternSearchLayer(); - }, [embeddable, layerList]); - - useEffect(() => { - async function setupEmbeddable() { - if (!factory) { - // eslint-disable-next-line no-console - console.error('Map embeddable not found.'); - return; - } - const input: MapEmbeddableInput = { - id: htmlIdGenerator()(), - attributes: { title: '' }, - filters: data.query.filterManager.getFilters() ?? [], - hidePanelTitles: true, - viewMode: ViewMode.VIEW, - isLayerTOCOpen: false, - hideFilterActions: true, - // can use mapSettings to center map on anomalies - mapSettings: { - disableInteractive: false, - hideToolbarOverlay: false, - hideLayerControl: false, - hideViewControl: false, - initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent - autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query - }, - }; - - const embeddableObject = await factory.create(input); - - if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { - const basemapLayerDescriptor = mapsPlugin - ? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor() - : null; - - if (basemapLayerDescriptor) { - baseLayers.current = [basemapLayerDescriptor]; - await embeddableObject.setLayerList(baseLayers.current); - } - } - - setEmbeddable(embeddableObject); - } - - setupEmbeddable(); - // we want this effect to execute exactly once after the component mounts - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) { - embeddable.updateInput(mapEmbeddableInput); - } - }, [embeddable, mapEmbeddableInput]); - - useEffect(() => { - if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) { - embeddable.setRenderTooltipContent(renderTooltipContent); - } - }, [embeddable, renderTooltipContent]); - - // We can only render after embeddable has already initialized - useEffect(() => { - if (embeddableRoot.current && embeddable) { - embeddable.render(embeddableRoot.current); - } - }, [embeddable, embeddableRoot]); - - if (!embeddablePlugin) { - // eslint-disable-next-line no-console - console.error('Embeddable start plugin not found'); - return null; - } - if (!mapsPlugin) { - // eslint-disable-next-line no-console - console.error('Maps start plugin not found'); - return null; - } - - return ( -
- ); -} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts b/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts deleted file mode 100644 index ee11a18345f64..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/components/embedded_map/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { EmbeddedMapComponent } from './embedded_map'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx index eec4522b96bd0..b1956f937d2cf 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content/geo_point_content.tsx @@ -10,15 +10,19 @@ import React, { useMemo } from 'react'; import type { Feature, Point } from 'geojson'; import type { FieldDataRowProps } from '../../stats_table/types/field_data_row'; import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; -import { EmbeddedMapComponent } from '../../embedded_map'; import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils'; import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; import { ExamplesList } from '../../examples_list'; import { ExpandedRowPanel } from '../../stats_table/components/field_data_expanded_row/expanded_row_panel'; +import { useDataVisualizerKibana } from '../../../../kibana_context'; export const DEFAULT_GEO_REGEX = RegExp('(?.+) (?.+)'); export const GeoPointContent: FC = ({ config }) => { + const { + services: { maps: mapsService }, + } = useDataVisualizerKibana(); + const formattedResults = useMemo(() => { const { stats } = config; @@ -54,7 +58,7 @@ export const GeoPointContent: FC = ({ config }) => { if (geoPointsFeatures.length > 0) { return { examples: formattedExamples, - layerList: [getGeoPointsLayer(geoPointsFeatures)], + pointsLayer: getGeoPointsLayer(geoPointsFeatures), }; } } @@ -62,12 +66,10 @@ export const GeoPointContent: FC = ({ config }) => { return ( - {formattedResults && Array.isArray(formattedResults.examples) && ( - - )} - {formattedResults && Array.isArray(formattedResults.layerList) && ( + {formattedResults?.examples && } + {mapsService && formattedResults?.pointsLayer && ( - + )} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx index 0dd142f827301..e8ee4bd99666a 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/expanded_row/geo_point_content_with_map/geo_point_content_with_map.tsx @@ -5,9 +5,11 @@ * 2.0. */ import type { FC } from 'react'; +import { useMemo } from 'react'; import React, { useEffect, useState } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { ES_GEO_FIELD_TYPE, LayerDescriptor } from '@kbn/maps-plugin/common'; +import { INITIAL_LOCATION } from '@kbn/maps-plugin/common'; import type { CombinedQuery } from '../../../../index_data_visualizer/types/combined_query'; import { ExpandedRowContent } from '../../stats_table/components/field_data_expanded_row/expanded_row_content'; import { DocumentStatsTable } from '../../stats_table/components/field_data_expanded_row/document_stats'; @@ -15,7 +17,6 @@ import { ExamplesList } from '../../examples_list'; import type { FieldVisConfig } from '../../stats_table/types'; import { useDataVisualizerKibana } from '../../../../kibana_context'; import { SUPPORTED_FIELD_TYPES } from '../../../../../../common/constants'; -import { EmbeddedMapComponent } from '../../embedded_map'; import { ExpandedRowPanel } from '../../stats_table/components/field_data_expanded_row/expanded_row_panel'; export const GeoPointContentWithMap: FC<{ @@ -31,84 +32,106 @@ export const GeoPointContentWithMap: FC<{ services: { maps: mapsPlugin, data }, } = useDataVisualizerKibana(); - // Update the layer list with updated geo points upon refresh - useEffect(() => { - async function updateIndexPatternSearchLayer() { - if ( - dataView?.id !== undefined && - config !== undefined && - config.fieldName !== undefined && - (config.type === SUPPORTED_FIELD_TYPES.GEO_POINT || - config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE) - ) { - const params = { - indexPatternId: dataView.id, - geoFieldName: config.fieldName, - geoFieldType: config.type as ES_GEO_FIELD_TYPE, - filters: data.query.filterManager.getFilters() ?? [], + const query = useMemo(() => { + return combinedQuery + ? { + query: combinedQuery.searchString, + language: combinedQuery.searchQueryLanguage, + } + : undefined; + }, [combinedQuery]); - ...(typeof esql === 'string' ? { esql, type: 'ESQL' } : {}), - ...(combinedQuery - ? { - query: { - query: combinedQuery.searchString, - language: combinedQuery.searchQueryLanguage, - }, - } - : {}), - }; - const searchLayerDescriptor = mapsPlugin - ? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params) - : null; + useEffect(() => { + if (!mapsPlugin) { + return; + } - if (searchLayerDescriptor?.sourceDescriptor) { - if (esql !== undefined) { - // Currently, createESSearchSourceLayerDescriptor doesn't support ES|QL yet - // but we can manually override the source descriptor with the ES|QL ESQLSourceDescriptor - const esqlSourceDescriptor = { - columns: [ - { - name: config.fieldName, - type: config.type, - }, - ], - dataViewId: dataView.id, - dateField: dataView.timeFieldName ?? timeFieldName, - geoField: config.fieldName, - esql, - narrowByGlobalSearch: true, - narrowByGlobalTime: true, - narrowByMapBounds: true, - id: searchLayerDescriptor.sourceDescriptor.id, - type: 'ESQL', - applyForceRefresh: true, - }; + if ( + !dataView?.id || + !config?.fieldName || + !( + config.type === SUPPORTED_FIELD_TYPES.GEO_POINT || + config.type === SUPPORTED_FIELD_TYPES.GEO_SHAPE + ) + ) { + setLayerList([]); + return; + } - setLayerList([ - ...layerList, + let ignore = false; + mapsPlugin.createLayerDescriptors + .createESSearchSourceLayerDescriptor({ + indexPatternId: dataView.id, + geoFieldName: config.fieldName, + geoFieldType: config.type as ES_GEO_FIELD_TYPE, + }) + .then((searchLayerDescriptor) => { + if (ignore) { + return; + } + if (esql !== undefined) { + // Currently, createESSearchSourceLayerDescriptor doesn't support ES|QL yet + // but we can manually override the source descriptor with the ES|QL ESQLSourceDescriptor + const esqlSourceDescriptor = { + columns: [ { - ...searchLayerDescriptor, - sourceDescriptor: esqlSourceDescriptor, + name: config.fieldName, + type: config.type, }, - ]); - } else { - setLayerList([...layerList, searchLayerDescriptor]); - } + ], + dataViewId: dataView.id, + dateField: dataView.timeFieldName ?? timeFieldName, + geoField: config.fieldName, + esql, + narrowByGlobalSearch: true, + narrowByGlobalTime: true, + narrowByMapBounds: true, + id: searchLayerDescriptor.sourceDescriptor!.id, + type: 'ESQL', + applyForceRefresh: true, + }; + + setLayerList([ + { + ...searchLayerDescriptor, + sourceDescriptor: esqlSourceDescriptor, + }, + ]); + } else { + setLayerList([searchLayerDescriptor]); } - } - } - updateIndexPatternSearchLayer(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataView, combinedQuery, esql, config, mapsPlugin, data.query]); + }) + .catch(() => { + if (!ignore) { + setLayerList([]); + } + }); + + return () => { + ignore = true; + }; + }, [dataView, combinedQuery, esql, config, mapsPlugin, timeFieldName]); if (stats?.examples === undefined) return null; return ( - - - + {mapsPlugin && ( + + + + )} ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx index c73ac26938e85..e326f3a4f79f0 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; -import { EuiText, htmlIdGenerator } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { VectorLayerDescriptor } from '@kbn/maps-plugin/common'; @@ -21,7 +21,6 @@ import { import type { EMSTermJoinConfig } from '@kbn/maps-plugin/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { useDataVisualizerKibana } from '../../../../../kibana_context'; -import { EmbeddedMapComponent } from '../../../embedded_map'; import type { FieldVisStats } from '../../../../../../../common/types'; import { ExpandedRowPanel } from './expanded_row_panel'; @@ -31,7 +30,7 @@ export const getChoroplethTopValuesLayer = ( { layerId, field }: EMSTermJoinConfig ): VectorLayerDescriptor => { return { - id: htmlIdGenerator()(), + id: 'choroplethLayer', label: i18n.translate('xpack.dataVisualizer.choroplethMap.topValuesCount', { defaultMessage: 'Top values count for {fieldName}', values: { fieldName }, @@ -41,7 +40,7 @@ export const getChoroplethTopValuesLayer = ( // Left join is the id from the type of field (e.g. world_countries) leftField: field, right: { - id: 'anomaly_count', + id: 'doc_count', type: SOURCE_TYPES.TABLE_SOURCE, __rows: topValues, __columns: [ @@ -103,13 +102,14 @@ export const ChoroplethMap: FC = ({ stats, suggestion }) => { const { services: { data: { fieldFormats }, + maps: mapsService, }, } = useDataVisualizerKibana(); const { fieldName, isTopValuesSampled, topValues, sampleCount } = stats; - const layerList: VectorLayerDescriptor[] = useMemo( - () => [getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion)], + const choroplethLayer: VectorLayerDescriptor = useMemo( + () => getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion), [suggestion, fieldName, topValues] ); @@ -157,9 +157,11 @@ export const ChoroplethMap: FC = ({ stats, suggestion }) => { className={'dvPanel__wrapper'} grow={true} > -
- -
+ {mapsService && ( +
+ +
+ )} {countsElement} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 7482e686db33e..d5f8f17b9ee17 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -32,7 +32,7 @@ import { UrlStateProvider, } from '@kbn/ml-url-state'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { ENABLE_ESQL } from '@kbn/discover-utils'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { getCoreStart, getPluginsStart } from '../../kibana_services'; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index 428311f87b305..61a7a80484eb9 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -78,7 +78,6 @@ "@kbn/visualization-utils", "@kbn/ml-time-buckets", "@kbn/aiops-log-rate-analysis", - "@kbn/discover-utils", "@kbn/react-kibana-context-render", "@kbn/react-kibana-context-theme" ], diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_data_stream.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_data_stream.test.ts index 7504a4b55c1cb..8ae7df34888d5 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_data_stream.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_data_stream.test.ts @@ -100,7 +100,7 @@ describe('ResultsDataStream', () => { it('should not install space if install not executed', async () => { const resultsDataStream = new ResultsDataStream({ kibanaVersion: '8.13.0' }); - expect(resultsDataStream.installSpace('space1')).rejects.toThrowError(); + await expect(resultsDataStream.installSpace('space1')).rejects.toThrowError(); }); it('should throw error if main install had error', async () => { @@ -115,7 +115,7 @@ describe('ResultsDataStream', () => { (dataStreamSpacesAdapter.install as jest.Mock).mockRejectedValueOnce(error); await resultsDataStream.install(params); - expect(resultsDataStream.installSpace('space1')).rejects.toThrowError(error); + await expect(resultsDataStream.installSpace('space1')).rejects.toThrowError(error); }); }); }); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts index cb6fe9da7c276..f7b2fa3415147 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts @@ -48,13 +48,15 @@ export class EcsDataQualityDashboardPlugin public setup(core: CoreSetup, plugins: PluginSetupDependencies) { this.logger.debug('ecsDataQualityDashboard: Setup'); - this.resultsDataStream.install({ - esClient: core - .getStartServices() - .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), - logger: this.logger, - pluginStop$: this.pluginStop$, - }); + this.resultsDataStream + .install({ + esClient: core + .getStartServices() + .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), + logger: this.logger, + pluginStop$: this.pluginStop$, + }) + .catch(() => {}); // it shouldn't reject, but just in case core.http.registerRouteHandlerContext< DataQualityDashboardRequestHandlerContext, diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts index eeb3c6cc4289d..9cc3efb03e195 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_service/index.ts @@ -318,7 +318,7 @@ export class AIAssistantService { if (!anonymizationFieldsIndexName) { await this.anonymizationFieldsDataStream.installSpace(spaceId); - this.createDefaultAnonymizationFields(spaceId); + await this.createDefaultAnonymizationFields(spaceId); } } catch (error) { this.options.logger.error( diff --git a/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts b/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts index 1805df38b789b..bacdd6cac1b49 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/executor.test.ts @@ -17,14 +17,14 @@ import { KibanaRequest } from '@kbn/core-http-server'; import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common'; import { loggerMock } from '@kbn/logging-mocks'; -import { handleStreamStorage } from './parse_stream'; +import * as ParseStream from './parse_stream'; const request = { body: { subAction: 'invokeAI', message: 'hello', }, } as KibanaRequest; -const onLlmResponse = jest.fn(); +const onLlmResponse = jest.fn(async () => {}); // We need it to be a promise, or it'll crash because of missing `.catch` const connectorId = 'testConnectorId'; const mockLogger = loggerMock.create(); const testProps: Omit = { @@ -38,9 +38,8 @@ const testProps: Omit = { onLlmResponse, logger: mockLogger, }; -jest.mock('./parse_stream'); -const mockHandleStreamStorage = handleStreamStorage as jest.Mock; +const handleStreamStorageSpy = jest.spyOn(ParseStream, 'handleStreamStorage'); describe('executeAction', () => { beforeEach(() => { @@ -83,7 +82,7 @@ describe('executeAction', () => { JSON.stringify(readableStream.pipe(new PassThrough())) ); - expect(mockHandleStreamStorage).toHaveBeenCalledWith({ + expect(handleStreamStorageSpy).toHaveBeenCalledWith({ actionTypeId: '.bedrock', onMessageSent: onLlmResponse, logger: mockLogger, diff --git a/x-pack/plugins/elastic_assistant/server/lib/executor.ts b/x-pack/plugins/elastic_assistant/server/lib/executor.ts index b8fa9fe0c5170..e7797805854ec 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/executor.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/executor.ts @@ -92,7 +92,7 @@ export const executeAction = async ({ logger, responseStream: readable, abortSignal, - }); + }).catch(() => {}); return readable.pipe(new PassThrough()); }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts index 103c0932f86c3..7664e970b4df8 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.test.ts @@ -221,7 +221,7 @@ describe('callAgentExecutor', () => { mockInvokeWithChainCallback({ ...props, agentType }, more), }) ); - const onLlmResponse = jest.fn(); + const onLlmResponse = jest.fn(async () => {}); // We need it to be a promise, or it'll crash because of missing `.catch` await callAgentExecutor({ ...defaultProps, onLlmResponse, isStream: true }); expect(onLlmResponse).toHaveBeenCalledWith( @@ -250,7 +250,7 @@ describe('callAgentExecutor', () => { mockInvokeWithChainCallback({ ...props, agentType }, more), }) ); - const onLlmResponse = jest.fn(); + const onLlmResponse = jest.fn(async () => {}); // We need it to be a promise, or it'll crash because of missing `.catch` await callAgentExecutor({ ...defaultProps, onLlmResponse, isStream: true }); expect(mockPush).toHaveBeenCalledWith({ payload: 'hi', type: 'content' }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts index e6863fa5550fe..fdecc1365d43c 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts @@ -162,7 +162,7 @@ export const callAgentExecutor: AgentExecutor = async ({ traceId: streamingSpan?.ids?.['trace.id'], }, isError - ); + ).catch(() => {}); } streamEnd(); didEnd = true; diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts index 32f5fc47ae770..e579996eb13d1 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts @@ -378,7 +378,7 @@ export const postActionsConnectorExecuteRoute = ( logger.error(err); const error = transformError(err); if (onLlmResponse) { - onLlmResponse(error.message, {}, true); + await onLlmResponse(error.message, {}, true); } telemetry.reportEvent(INVOKE_ASSISTANT_ERROR_EVENT.eventType, { actionTypeId: request.body.actionTypeId, diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index 1bc30c41b6b22..9aa0c8ab2f973 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; + import type { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; import type { EncryptedSavedObjectsClient, @@ -33,8 +35,10 @@ function createEncryptedSavedObjectsStartMock() { function createEncryptedSavedObjectsClientMock(opts?: EncryptedSavedObjectsClientOptions) { return { getDecryptedAsInternalUser: jest.fn(), - createPointInTimeFinderDecryptedAsInternalUser: jest.fn(), - } as jest.Mocked; + createPointInTimeFinderDecryptedAsInternalUser: jest.fn((findOptions, deps) => + savedObjectsClientMock.create().createPointInTimeFinder(findOptions, deps) + ), + } as unknown as jest.Mocked; } export const encryptedSavedObjectsMock = { diff --git a/x-pack/plugins/encrypted_saved_objects/tsconfig.json b/x-pack/plugins/encrypted_saved_objects/tsconfig.json index 7b9087064c109..17dea87aca6ee 100644 --- a/x-pack/plugins/encrypted_saved_objects/tsconfig.json +++ b/x-pack/plugins/encrypted_saved_objects/tsconfig.json @@ -11,6 +11,7 @@ "@kbn/utility-types", "@kbn/core-saved-objects-server", "@kbn/core-saved-objects-base-server-internal", + "@kbn/core-saved-objects-api-server-mocks", ], "exclude": [ "target/**/*", 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 fb17854f6f674..bae0aacec6ff4 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 @@ -259,7 +259,7 @@ describe('callEnterpriseSearchConfigAPI', () => { jest.useFakeTimers({ legacyFakeTimers: true }); // Warning - callEnterpriseSearchConfigAPI(mockDependencies); + void callEnterpriseSearchConfigAPI(mockDependencies); jest.advanceTimersByTime(150); expect(mockDependencies.log.warn).toHaveBeenCalledWith( 'Enterprise Search access check took over 100ms. Please ensure your Enterprise Search server is responding normally and not adversely impacting Kibana load speeds.' diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline.test.ts index 62fc7935b169a..7c3104dc84928 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline.test.ts @@ -78,7 +78,7 @@ describe('createMlInferencePipeline lib function', () => { ); }); - it('should throw an error without creating the pipeline if it already exists', () => { + it('should throw an error without creating the pipeline if it already exists', async () => { mockClient.ingest.getPipeline.mockImplementation(() => Promise.resolve({ [inferencePipelineGeneratedName]: {}, @@ -91,7 +91,7 @@ describe('createMlInferencePipeline lib function', () => { mockClient as unknown as ElasticsearchClient ); - expect(actualResult).rejects.toThrow(Error); + await expect(actualResult).rejects.toThrow(Error); expect(mockClient.ingest.putPipeline).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts index 020d717417820..157c9f13ca055 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts @@ -727,8 +727,8 @@ describe('fetchMlInferencePipelineProcessors lib function', () => { }); describe('when Machine Learning is disabled in the current space', () => { - it('should throw an error', () => { - expect(() => + it('should throw an error', async () => { + await expect(() => fetchMlInferencePipelineProcessors( mockClient as unknown as ElasticsearchClient, undefined, diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts index 5e2c472dab7ac..9e017424a8f3d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/fetch_ml_models.test.ts @@ -35,8 +35,8 @@ describe('fetchMlModels', () => { })); }); - it('errors when there is no trained model provider', () => { - expect(() => fetchMlModels(undefined, mockLogger)).rejects.toThrowError( + it('errors when there is no trained model provider', async () => { + await expect(() => fetchMlModels(undefined, mockLogger)).rejects.toThrowError( 'Machine Learning is not enabled' ); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts index 0b0a42630bcc7..9cc66bdb609f9 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.test.ts @@ -22,8 +22,8 @@ describe('getMlModelDeploymentStatus', () => { jest.clearAllMocks(); }); - it('should error when there is no trained model provider', () => { - expect(() => getMlModelDeploymentStatus('mockModelName', undefined)).rejects.toThrowError( + it('should error when there is no trained model provider', async () => { + await expect(() => getMlModelDeploymentStatus('mockModelName', undefined)).rejects.toThrowError( 'Machine Learning is not enabled' ); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts index 67a5c75f0f437..9111bee449ec8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_deployment.test.ts @@ -26,8 +26,8 @@ describe('startMlModelDeployment', () => { jest.clearAllMocks(); }); - it('should error when there is no trained model provider', () => { - expect(() => startMlModelDeployment(modelName, undefined)).rejects.toThrowError( + it('should error when there is no trained model provider', async () => { + await expect(() => startMlModelDeployment(modelName, undefined)).rejects.toThrowError( 'Machine Learning is not enabled' ); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts index 80318eab0ce77..51023f643504e 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/start_ml_model_download.test.ts @@ -26,8 +26,8 @@ describe('startMlModelDownload', () => { jest.clearAllMocks(); }); - it('should error when there is no trained model provider', () => { - expect(() => startMlModelDownload(knownModelName, undefined)).rejects.toThrowError( + it('should error when there is no trained model provider', async () => { + await expect(() => startMlModelDownload(knownModelName, undefined)).rejects.toThrowError( 'Machine Learning is not enabled' ); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts index 0765fafcc9d1d..89ab6abe8ec77 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts @@ -25,8 +25,8 @@ describe('getMlInferencePipelines', () => { jest.clearAllMocks(); }); - it('should throw an error if Machine Learning is disabled in the current space', () => { - expect(() => + it('should throw an error if Machine Learning is disabled in the current space', async () => { + await expect(() => getMlInferencePipelines(mockClient as unknown as ElasticsearchClient, undefined) ).rejects.toThrowError('Machine Learning is not enabled'); }); diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index bd1498e0b0b61..24298c55fdffa 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -274,11 +274,11 @@ export class EnterpriseSearchPlugin implements Plugin { registerStatsRoutes(dependencies); // Analytics Routes (stand-alone product) - getStartServices().then(([coreStart, { data }]) => { + void getStartServices().then(([coreStart, { data }]) => { registerAnalyticsRoutes({ ...dependencies, data, savedObjects: coreStart.savedObjects }); }); - getStartServices().then(([, { security: securityStart }]) => { + void getStartServices().then(([, { security: securityStart }]) => { registerApiKeysRoutes(dependencies, securityStart); }); @@ -292,7 +292,7 @@ export class EnterpriseSearchPlugin implements Plugin { } let savedObjectsStarted: SavedObjectsServiceStart; - getStartServices().then(([coreStart]) => { + void getStartServices().then(([coreStart]) => { savedObjectsStarted = coreStart.savedObjects; if (usageCollection) { diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts index cec2262c95a2e..4df97172fa0d5 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/adaptive_relevance.test.ts @@ -27,8 +27,8 @@ describe('search relevance insights routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); @@ -51,8 +51,8 @@ describe('search relevance insights routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, body: { query: 'some query', @@ -80,8 +80,8 @@ describe('search relevance insights routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); @@ -104,8 +104,8 @@ describe('search relevance insights routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, body: { curation: { enabled: true } }, }); @@ -129,8 +129,8 @@ describe('search relevance insights routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine', query: 'foo' }, query: { type: 'curation' }, }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts index cd1221ec52b80..f86001832a3c6 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts @@ -34,8 +34,8 @@ describe('engine routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute(mockRequest); + it('creates a request handler', async () => { + await mockRouter.callRoute(mockRequest); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/as/engines/collection', @@ -44,7 +44,7 @@ describe('engine routes', () => { }); describe('hasValidData', () => { - it('should correctly validate that the response has data', () => { + it('should correctly validate that the response has data', async () => { mockRequestHandler.createRequest.mockClear(); const response = { meta: { @@ -55,15 +55,15 @@ describe('engine routes', () => { results: [], }; - mockRouter.callRoute(mockRequest); + await mockRouter.callRoute(mockRequest); expect(mockRequestHandler.hasValidData(response)).toBe(true); }); - it('should correctly validate that a response does not have data', () => { + it('should correctly validate that a response does not have data', async () => { mockRequestHandler.createRequest.mockClear(); const response = {}; - mockRouter.callRoute(mockRequest); + await mockRouter.callRoute(mockRequest); expect(mockRequestHandler.hasValidData(response)).toBe(false); }); }); @@ -114,8 +114,8 @@ describe('engine routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({ body: { name: 'some-engine', language: 'en' } }); + it('creates a request handler', async () => { + await mockRouter.callRoute({ body: { name: 'some-engine', language: 'en' } }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/as/engines/collection', }); @@ -192,8 +192,8 @@ describe('engine routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({ + it('creates a request handler', async () => { + await mockRouter.callRoute({ body: { name: 'some-elasticindexed-engine', search_index: { type: 'elasticsearch', index_name: 'search-elastic-index' }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/onboarding.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/onboarding.test.ts index 8990a9e1eeab4..bb281983d5382 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/onboarding.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/onboarding.test.ts @@ -26,8 +26,8 @@ describe('engine routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({ body: {} }); + it('creates a request handler', async () => { + await mockRouter.callRoute({ body: {} }); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/as/onboarding/complete', hasJsonResponse: false, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts index d82eb06fce68e..fcbf10c080b30 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts @@ -35,8 +35,8 @@ describe('result settings routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); @@ -59,8 +59,8 @@ describe('result settings routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, body: { result_settings: resultFields, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts index 56e60db543c5d..5b6395912766f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts @@ -73,8 +73,8 @@ describe('search settings routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); @@ -97,8 +97,8 @@ describe('search settings routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, body: searchSettings, }); @@ -122,8 +122,8 @@ describe('search settings routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_ui.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_ui.test.ts index e92dc63952eb5..1cfb52640c09b 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_ui.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_ui.test.ts @@ -27,8 +27,8 @@ describe('reference application routes', () => { }); }); - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ + it('creates a request to enterprise search', async () => { + await mockRouter.callRoute({ params: { engineName: 'some-engine' }, }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts index ab9d5e20cfe92..af703c1d0ceee 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts @@ -31,8 +31,8 @@ describe('oauth routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({}); + it('creates a request handler', async () => { + await mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/oauth/authorize', @@ -76,8 +76,8 @@ describe('oauth routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({}); + it('creates a request handler', async () => { + await mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/oauth/authorize', @@ -117,8 +117,8 @@ describe('oauth routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({}); + it('creates a request handler', async () => { + await mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/oauth/authorize', diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/security.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/security.test.ts index 744f0e689cd82..ebcbba2767be3 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/security.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/security.test.ts @@ -27,8 +27,8 @@ describe('security routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({}); + it('creates a request handler', async () => { + await mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/security', @@ -53,8 +53,8 @@ describe('security routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute({}); + it('creates a request handler', async () => { + await mockRouter.callRoute({}); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/org/security/source_restrictions', diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index ea8caede378de..06b6fce3baee5 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -556,7 +556,7 @@ describe('sources routes', () => { }); }); - it('creates a request handler', () => { + it('creates a request handler', async () => { const mockRequest = { params: { sourceId: '123', @@ -564,7 +564,7 @@ describe('sources routes', () => { }, }; - mockRouter.callRoute(mockRequest); + await mockRouter.callRoute(mockRequest); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/sources/:sourceId/reindex_job/:jobId', @@ -1343,8 +1343,8 @@ describe('sources routes', () => { }); }); - it('creates a request handler', () => { - mockRouter.callRoute(mockRequest as any); + it('creates a request handler', async () => { + await mockRouter.callRoute(mockRequest as any); expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ path: '/ws/sources/create', diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index d44ae68745245..5abd44061eb4d 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -167,7 +167,7 @@ describe('EventLogStart', () => { total: 0, data: [], }); - expect( + await expect( eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'], { start: 'not a date string', }) @@ -181,7 +181,7 @@ describe('EventLogStart', () => { total: 0, data: [], }); - expect( + await expect( eventLogClient.findEventsBySavedObjectIds('saved-object-type', ['saved-object-id'], { end: 'not a date string', }) diff --git a/x-pack/plugins/event_log/server/event_logger.test.ts b/x-pack/plugins/event_log/server/event_logger.test.ts index a9295ada0dd85..ccf5b9a39d7d5 100644 --- a/x-pack/plugins/event_log/server/event_logger.test.ts +++ b/x-pack/plugins/event_log/server/event_logger.test.ts @@ -48,7 +48,7 @@ describe('EventLogger', () => { eventLogger.logEvent({}); await waitForLogEvent(systemLogger); - delay(WRITE_LOG_WAIT_MILLIS); // sleep a bit since event logging is async + await delay(WRITE_LOG_WAIT_MILLIS); // sleep a bit since event logging is async expect(esContext.esAdapter.indexDocument).toHaveBeenCalled(); }); @@ -61,7 +61,7 @@ describe('EventLogger', () => { eventLogger.logEvent({}); await waitForLogEvent(systemLogger); - delay(WRITE_LOG_WAIT_MILLIS); // sleep a bit longer since event logging is async + await delay(WRITE_LOG_WAIT_MILLIS); // sleep a bit longer since event logging is async expect(esContext.esAdapter.indexDocument).toHaveBeenCalled(); expect(esContext.esAdapter.indexDocuments).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 4b0dcbece34b4..b5a6a6bcbe37d 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -111,11 +111,18 @@ export class Plugin implements CorePlugin { - if (!success) { - this.systemLogger.error(`initialization failed, events will not be indexed`); - } - }); + this.esContext + .waitTillReady() + .then((success) => { + if (!success) { + this.systemLogger.error(`initialization failed, events will not be indexed`); + } + }) + .catch((error) => { + this.systemLogger.error( + `initialization failed with error: ${error}. Events will not be indexed` + ); + }); // will log the event after initialization this.eventLogger.logEvent({ diff --git a/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts b/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts index d3909a904c26f..b774ac5eb7a97 100644 --- a/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts +++ b/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts @@ -18,7 +18,7 @@ export function initFileUploadTelemetry( ) { coreSetup.savedObjects.registerType(telemetryMappingsType); registerUsageCollector(usageCollection); - coreSetup.getStartServices().then(([core]) => { + void coreSetup.getStartServices().then(([core]) => { setInternalRepository(core.savedObjects.createInternalRepository); }); } diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index f6b6673ead2ca..8c5868616e2d4 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -98,6 +98,14 @@ export const calculateAuthz = ({ ? !!(fleet.agents?.all && fleet.agentPolicies?.all && fleet.settings?.all) : fleet.all; + const writeIntegrationPolicies = subfeatureEnabled + ? (fleet.agentPolicies?.all && integrations.all) ?? false + : ((fleet.all || fleet.agentPolicies?.all) ?? false) && integrations.all; + const readIntegrationPolicies = subfeatureEnabled + ? (fleet.agentPolicies?.read && (integrations.all || integrations.read)) ?? false + : ((fleet.all || fleet.read || fleet.agentPolicies?.read) ?? false) && + (integrations.all || integrations.read); + // TODO remove fallback when the feature flag is removed const fleetAuthz: FleetAuthz['fleet'] = subfeatureEnabled ? { @@ -140,14 +148,6 @@ export const calculateAuthz = ({ (fleet.all || fleet.read || fleet.setup || fleet.agentPolicies?.read) ?? false, }; - const writeIntegrationPolicies = subfeatureEnabled - ? (fleet.agentPolicies?.all && integrations.all) ?? false - : ((fleet.all || fleet.agentPolicies?.all) ?? false) && integrations.all; - const readIntegrationPolicies = subfeatureEnabled - ? (fleet.agentPolicies?.read && (integrations.all || integrations.read)) ?? false - : ((fleet.all || fleet.read || fleet.agentPolicies?.read) ?? false) && - (integrations.all || integrations.read); - return { fleet: fleetAuthz, integrations: { diff --git a/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts b/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts index afab363f87ad4..d609379cc5bdd 100644 --- a/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/privileges_editor_role.cy.ts @@ -11,7 +11,7 @@ import { login, loginWithUserAndWaitForPage, logout } from '../tasks/login'; import { getIntegrationCard } from '../screens/integrations'; -import { LANDING_PAGE_ADD_FLEET_SERVER_BUTTON } from '../screens/fleet'; +import { ADD_AGENT_BUTTON, FLEET_SERVER_MISSING_PRIVILEGES } from '../screens/fleet'; import { ADD_INTEGRATION_POLICY_BTN } from '../screens/integrations'; import { scrollToIntegration } from '../tasks/integrations'; @@ -40,9 +40,30 @@ describe('When the user has Editor built-in role', () => { navigateTo(FLEET); }); - it('It should not show a callout', () => { + it('It should not show a callout if fleet server is setup', () => { + cy.intercept('GET', '/api/fleet/agents/setup', { + body: { + isReady: true, + is_secrets_storage_enabled: true, + missing_requirements: [], + missing_optional_features: [], + }, + }); loginWithUserAndWaitForPage(FLEET, BuiltInEditorUser); - cy.getBySel(LANDING_PAGE_ADD_FLEET_SERVER_BUTTON).should('exist'); + cy.getBySel(ADD_AGENT_BUTTON).should('exist'); + }); + + it('It should not show a callout with missing manage_service_accout if fleet server is not setup', () => { + cy.intercept('GET', '/api/fleet/agents/setup', { + body: { + isReady: true, + is_secrets_storage_enabled: true, + missing_requirements: ['fleet_server'], + missing_optional_features: [], + }, + }); + loginWithUserAndWaitForPage(FLEET, BuiltInEditorUser); + cy.getBySel(FLEET_SERVER_MISSING_PRIVILEGES.PROMPT).should('exist'); }); }); diff --git a/x-pack/plugins/fleet/cypress/tasks/privileges.ts b/x-pack/plugins/fleet/cypress/tasks/privileges.ts index 0f8aba10e8fcb..214bd0f14e6e6 100644 --- a/x-pack/plugins/fleet/cypress/tasks/privileges.ts +++ b/x-pack/plugins/fleet/cypress/tasks/privileges.ts @@ -60,6 +60,7 @@ export const FleetAllIntegrAllRole: Role = { privileges: ['all'], }, ], + cluster: ['manage_service_account'], }, kibana: [ { @@ -89,6 +90,7 @@ export const FleetAllIntegrReadRole: Role = { privileges: ['all'], }, ], + cluster: ['manage_service_account'], }, kibana: [ { @@ -116,6 +118,7 @@ export const FleetAllIntegrNoneRole: Role = { privileges: ['all'], }, ], + cluster: ['manage_service_account'], }, kibana: [ { @@ -143,6 +146,7 @@ export const FleetAgentsReadIntegrNoneRole: Role = { privileges: ['all'], }, ], + cluster: ['manage_service_account'], }, kibana: [ { @@ -170,6 +174,7 @@ export const FleetNoneIntegrAllRole: Role = { privileges: ['all'], }, ], + cluster: ['manage_service_account'], }, kibana: [ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx index 629fe3c030067..511f5d05154fc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx @@ -21,10 +21,10 @@ import { EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; - import styled from 'styled-components'; -import { useStartServices, useFlyoutContext } from '../../hooks'; +import { useStartServices, useFlyoutContext, useCheckPermissions } from '../../hooks'; +import { FleetServerMissingESPrivileges } from '../../sections/agents/components'; import { QuickStartTab } from './quick_start_tab'; import { AdvancedTab } from './advanced_tab'; @@ -121,6 +121,17 @@ const Header: React.FunctionComponent<{ export const FleetServerFlyout: React.FunctionComponent = ({ onClose }) => { const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(onClose); + const { permissionsError, isPermissionsLoading } = useCheckPermissions(); + + let errorContent: React.ReactNode | undefined; + if (permissionsError === 'MISSING_FLEET_SERVER_SETUP_PRIVILEGES') { + errorContent = ( + + + + ); + } + return ( @@ -132,7 +143,9 @@ export const FleetServerFlyout: React.FunctionComponent = ({ onClose }) = /> - {currentTabContent} + + {isPermissionsLoading ? null : errorContent ? errorContent : currentTabContent} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx index 7cb5ae48dbdec..b202bb7bf6cbb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx @@ -27,7 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { MultiRowInput } from '../../../sections/settings/components/multi_row_input'; -import { useLink } from '../../../hooks'; +import { useAuthz, useLink } from '../../../hooks'; import type { QuickStartCreateForm } from '../hooks'; import { FleetServerHostSelect } from '../components'; @@ -53,6 +53,10 @@ const GettingStartedStepContent: React.FunctionComponent = onClose, }) => { const { getHref } = useLink(); + const authz = useAuthz(); + const canWritePolicies = + authz.fleet.allAgentPolicies && authz.integrations.writeIntegrationPolicies; + const isDisabled = fleetServerHosts.length === 0 && !canWritePolicies; if (status === 'success') { return ( @@ -143,6 +147,7 @@ const GettingStartedStepContent: React.FunctionComponent = defaultMessage: 'Specify name', })} {...inputs.nameInput.props} + disabled={isDisabled} /> = = isLoading={status === 'loading'} onClick={submit} data-test-subj="generateFleetServerPolicyButton" + disabled={isDisabled} > {fleetServerHosts.length > 0 ? ( { - const { data: permissionsError, status } = useQuery( - ['fetch-check-permissions'], - checkPermissions - ); - return { isPermissionsLoading: status === 'loading', permissionsError }; + const { data, isInitialLoading } = useQuery(['fetch-check-permissions'], checkPermissions); + return { isPermissionsLoading: isInitialLoading, permissionsError: data?.error }; }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts index 54afb2b778a95..e298003ea7967 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts @@ -51,6 +51,6 @@ export const useIsFirstTimeAgentUserQuery = (): UseIsFirstTimeAgentUserResponse return { isLoading: authz.fleet.readAgentPolicies && (areAgentPoliciesLoading || areAgentsLoading), - isFirstTimeAgentUser: !authz.fleet.readAgentPolicies && agents?.data?.total === 0, + isFirstTimeAgentUser: authz.fleet.readAgentPolicies && agents?.data?.total === 0, }; }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index 9b42fdd42f10c..be59e2d64f15c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -20,6 +20,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react'; +import { policyHasFleetServer } from '../../../../../../../../common/services'; + import { InstallStatus } from '../../../../../types'; import type { GetAgentPoliciesResponseItem, InMemoryPackagePolicy } from '../../../../../types'; import { @@ -113,6 +115,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; const canAddAgents = useAuthz().fleet.addAgents; + const canAddFleetServers = useAuthz().fleet.addFleetServers; const packageAndAgentPolicies = useMemo((): Array<{ agentPolicy?: GetAgentPoliciesResponseItem; @@ -264,12 +267,15 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps if (!agentPolicy) { return null; } + const canAddAgentsForPolicy = policyHasFleetServer(agentPolicy) + ? canAddFleetServers + : canAddAgents; return ( setFlyoutOpenForPolicyId(agentPolicy.id)} - canAddAgents={canAddAgents} + canAddAgents={canAddAgentsForPolicy} hasHelpPopover={showAddAgentHelpForPackagePolicyId === packagePolicy.id} /> ); @@ -301,7 +307,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps }, }, ], - [getHref, canWriteIntegrationPolicies, canAddAgents, showAddAgentHelpForPackagePolicyId] + [ + getHref, + canWriteIntegrationPolicies, + canAddAgents, + canAddFleetServers, + showAddAgentHelpForPackagePolicyId, + ] ); const noItemsMessage = useMemo(() => { diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index e7f54fc1b8c54..23ae673ad892a 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -10,8 +10,8 @@ import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { AgentPolicy, InMemoryPackagePolicy } from '../types'; - import { useAgentPolicyRefresh, useAuthz, useLink } from '../hooks'; +import { policyHasFleetServer } from '../services'; import { AgentEnrollmentFlyout } from './agent_enrollment_flyout'; import { ContextMenuActions } from './context_menu_actions'; @@ -35,8 +35,12 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ }) => { const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); const { getHref } = useLink(); - const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; - const canAddAgents = useAuthz().fleet.addAgents; + const authz = useAuthz(); + + const canWriteIntegrationPolicies = authz.integrations.writeIntegrationPolicies; + const isFleetServerPolicy = agentPolicy && policyHasFleetServer(agentPolicy); + + const canAddAgents = isFleetServerPolicy ? authz.fleet.addFleetServers : authz.fleet.addAgents; const refreshAgentPolicy = useAgentPolicyRefresh(); const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(defaultIsOpen); diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index aad61fd21c9ae..c24f7859b1189 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -613,12 +613,14 @@ export class FleetPlugin uninstallTokenService, }); licenseService.start(plugins.licensing.license$); - this.telemetryEventsSender.start(plugins.telemetry, core); - this.bulkActionsResolver?.start(plugins.taskManager); - this.fleetUsageSender?.start(plugins.taskManager); - this.checkDeletedFilesTask?.start({ taskManager: plugins.taskManager }); - startFleetUsageLogger(plugins.taskManager); - this.fleetMetricsTask?.start(plugins.taskManager, core.elasticsearch.client.asInternalUser); + this.telemetryEventsSender.start(plugins.telemetry, core).catch(() => {}); + this.bulkActionsResolver?.start(plugins.taskManager).catch(() => {}); + this.fleetUsageSender?.start(plugins.taskManager).catch(() => {}); + this.checkDeletedFilesTask?.start({ taskManager: plugins.taskManager }).catch(() => {}); + startFleetUsageLogger(plugins.taskManager).catch(() => {}); + this.fleetMetricsTask + ?.start(plugins.taskManager, core.elasticsearch.client.asInternalUser) + .catch(() => {}); const logger = appContextService.getLogger(); @@ -686,7 +688,7 @@ export class FleetPlugin ); // initialize (generate/encrypt/validate) Uninstall Tokens asynchronously - this.initializeUninstallTokens(); + this.initializeUninstallTokens().catch(() => {}); this.fleetStatus$.next({ level: ServiceStatusLevels.available, diff --git a/x-pack/plugins/fleet/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts index 765684a9adbe4..262bdc867df35 100644 --- a/x-pack/plugins/fleet/server/routes/app/index.ts +++ b/x-pack/plugins/fleet/server/routes/app/index.ts @@ -47,6 +47,20 @@ export const getCheckPermissionsHandler: FleetRequestHandler< error: 'MISSING_PRIVILEGES', } as CheckPermissionsResponse, }); + } else if (request.query.fleetServerSetup) { + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const { has_all_requested: hasAllPrivileges } = await esClient.security.hasPrivileges({ + body: { cluster: ['manage_service_account'] }, + }); + + if (!hasAllPrivileges) { + return response.ok({ + body: { + success: false, + error: 'MISSING_FLEET_SERVER_SETUP_PRIVILEGES', + } as CheckPermissionsResponse, + }); + } } return response.ok({ body: { success: true } as CheckPermissionsResponse }); @@ -142,7 +156,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => { .post({ path: APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN, fleetAuthz: { - fleet: { all: true }, + fleet: { allAgents: true }, }, }) .addVersion( @@ -159,7 +173,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => { .post({ path: APP_API_ROUTES.GENERATE_SERVICE_TOKEN_PATTERN_DEPRECATED, fleetAuthz: { - fleet: { all: true }, + fleet: { allAgents: true }, }, }) .addVersion( diff --git a/x-pack/plugins/fleet/server/routes/index.ts b/x-pack/plugins/fleet/server/routes/index.ts index d03703af22bb7..5177b85d84dea 100644 --- a/x-pack/plugins/fleet/server/routes/index.ts +++ b/x-pack/plugins/fleet/server/routes/index.ts @@ -27,7 +27,7 @@ import { registerRoutes as registerFleetProxiesRoutes } from './fleet_proxies'; import { registerRoutes as registerMessageSigningServiceRoutes } from './message_signing_service'; import { registerRoutes as registerUninstallTokenRoutes } from './uninstall_token'; -export async function registerRoutes(fleetAuthzRouter: FleetAuthzRouter, config: FleetConfigType) { +export function registerRoutes(fleetAuthzRouter: FleetAuthzRouter, config: FleetConfigType) { // Always register app routes for permissions checking registerAppRoutes(fleetAuthzRouter); diff --git a/x-pack/plugins/fleet/server/services/agent_policy_create.test.ts b/x-pack/plugins/fleet/server/services/agent_policy_create.test.ts index f541212a51ab1..1c569270a3c9d 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_create.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_create.test.ts @@ -192,15 +192,17 @@ describe('createAgentPolicyWithPackages', () => { it('should call deploy policy once when create policy with system package', async () => { mockedAgentPolicyService.deployPolicy.mockClear(); - mockedAgentPolicyService.create.mockImplementation((soClient, esClient, newPolicy, options) => { - if (!options?.skipDeploy) { - mockedAgentPolicyService.deployPolicy(soClientMock, 'new_id'); + mockedAgentPolicyService.create.mockImplementation( + async (soClient, esClient, newPolicy, options) => { + if (!options?.skipDeploy) { + await mockedAgentPolicyService.deployPolicy(soClientMock, 'new_id'); + } + return Promise.resolve({ + ...newPolicy, + id: options?.id || 'new_id', + } as AgentPolicy); } - return Promise.resolve({ - ...newPolicy, - id: options?.id || 'new_id', - } as AgentPolicy); - }); + ); const response = await createAgentPolicyWithPackages({ esClient: esClientMock, soClient: soClientMock, diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index d8834d5306a92..3abe4787e6132 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -83,12 +83,12 @@ export abstract class ActionRunner { // create task to check result with some delay, this runs in case of kibana crash too this.checkTaskId = await this.createCheckResultTask(); - withSpan({ name: this.getActionType(), type: 'action' }, () => + await withSpan({ name: this.getActionType(), type: 'action' }, () => this.processAgentsInBatches() - .then(() => { + .then(async () => { if (this.checkTaskId) { // no need for check task, action succeeded - this.bulkActionsResolver!.removeIfExists(this.checkTaskId); + await this.bulkActionsResolver!.removeIfExists(this.checkTaskId); } }) .catch(async (error) => { @@ -233,7 +233,7 @@ export abstract class ActionRunner { allAgentsProcessed += currentAgents.length; if (this.checkTaskId) { // updating check task with latest checkpoint (this.retryParams.searchAfter) - this.bulkActionsResolver?.removeIfExists(this.checkTaskId); + await this.bulkActionsResolver?.removeIfExists(this.checkTaskId); this.checkTaskId = await this.createCheckResultTask(); } } diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index 782b044a84697..8c711099264f4 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -355,7 +355,7 @@ describe('When using the artifacts services', () => { describe('and calling `deleteArtifact()`', () => { it('should delete the artifact', async () => { - deleteArtifact(esClientMock, '123'); + await deleteArtifact(esClientMock, '123'); expect(esClientMock.delete).toHaveBeenCalledWith({ index: FLEET_SERVER_ARTIFACTS_INDEX, @@ -375,7 +375,7 @@ describe('When using the artifacts services', () => { describe('and calling `bulkDeleteArtifacts()`', () => { it('should delete single artifact', async () => { - bulkDeleteArtifacts(esClientMock, ['123']); + await bulkDeleteArtifacts(esClientMock, ['123']); expect(esClientMock.bulk).toHaveBeenCalledWith({ refresh: 'wait_for', @@ -391,7 +391,7 @@ describe('When using the artifacts services', () => { }); it('should delete all the artifacts', async () => { - bulkDeleteArtifacts(esClientMock, ['123', '231']); + await bulkDeleteArtifacts(esClientMock, ['123', '231']); expect(esClientMock.bulk).toHaveBeenCalledWith({ refresh: 'wait_for', diff --git a/x-pack/plugins/fleet/server/services/download_source.test.ts b/x-pack/plugins/fleet/server/services/download_source.test.ts index e244ec80077b2..1e9c7275c8430 100644 --- a/x-pack/plugins/fleet/server/services/download_source.test.ts +++ b/x-pack/plugins/fleet/server/services/download_source.test.ts @@ -291,12 +291,12 @@ describe('Download Service', () => { }); describe('requireUniqueName', () => { - it('throws an error if the name already exists', () => { + it('throws an error if the name already exists', async () => { const soClient = getMockedSoClient({ defaultDownloadSourceId: 'download-source-test', sameName: true, }); - expect( + await expect( async () => await downloadSourceService.requireUniqueName(soClient, { name: 'Test' }) ).rejects.toThrow(`Download Source 'download-source-test' already exists with name 'Test'`); }); diff --git a/x-pack/plugins/fleet/server/services/epm/archive/extract.ts b/x-pack/plugins/fleet/server/services/epm/archive/extract.ts index 84c2457f4dafe..17118aa06b199 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/extract.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/extract.ts @@ -24,9 +24,9 @@ export async function untarBuffer( const inflateStream = tar.list().on('entry', (entry) => { const path = entry.path || ''; if (!filter({ path })) return; - streamToBuffer(entry as unknown as NodeJS.ReadableStream).then((entryBuffer) => - onEntry({ buffer: entryBuffer, path }) - ); + streamToBuffer(entry as unknown as NodeJS.ReadableStream) + .then((entryBuffer) => onEntry({ buffer: entryBuffer, path })) + .catch(() => {}); }); deflatedStream.pipe(inflateStream); diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts index 06b09e15951c4..3547fc70daa15 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts @@ -93,7 +93,7 @@ describe('installKibanaSavedObjects', () => { mockImporter.import.mockResolvedValueOnce(errorResponse).mockResolvedValueOnce(successResponse); - expect( + await expect( installKibanaSavedObjects({ savedObjectsImporter: mockImporter, logger: mockLogger, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts index 7568dafe248ac..dec78b3be7d5c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts @@ -325,7 +325,7 @@ describe('_installPackage', () => { describe('timeout not reached', () => { describe('force flag not provided', () => { it('throws concurrent installation error if force flag is not provided', async () => { - expect( + await expect( _installPackage({ savedObjectsClient: soClient, // @ts-ignore @@ -386,7 +386,7 @@ describe('_installPackage', () => { }); }); - it('surfaces saved object conflicts error', () => { + it('surfaces saved object conflicts error', async () => { appContextService.start( createAppContextStartContractMock({ internal: { @@ -407,7 +407,7 @@ describe('_installPackage', () => { new PackageSavedObjectConflictError('test') ); - expect( + await expect( _installPackage({ savedObjectsClient: soClient, // @ts-ignore diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index c8c2e542bfe8b..c5ce7e5f0eca8 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -1279,7 +1279,7 @@ export const saveKibanaAssetsRefs = async ( // Because Kibana assets are installed in parallel with ES assets with refresh: false, we almost always run into an // issue that causes a conflict error due to this issue: https://github.com/elastic/kibana/issues/126240. This is safe // to retry constantly until it succeeds to optimize this critical user journey path as much as possible. - pRetry( + await pRetry( () => savedObjectsClient.update( PACKAGES_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts index 8b129e6fe2f6c..6de0577e33238 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts @@ -153,7 +153,7 @@ describe('fetch package', () => { mockGetBundledPackageByName.mockResolvedValue(bundledPackage); - expect(() => fetchFindLatestPackageOrThrow('testpkg')).rejects.toBeInstanceOf( + await expect(() => fetchFindLatestPackageOrThrow('testpkg')).rejects.toBeInstanceOf( PackageNotFoundError ); }); diff --git a/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts index e13ff87bf45e5..e4e34d25671f6 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/requests.test.ts @@ -42,7 +42,7 @@ describe('Registry request', () => { (appContextService.getKibanaVersion as jest.Mock).mockReturnValue('8.0.0'); }); it('should set User-Agent header including kibana version', async () => { - getResponse(''); + await getResponse(''); expect(fetchMock).toHaveBeenCalledWith('', { headers: { @@ -54,7 +54,7 @@ describe('Registry request', () => { it('should set User-Agent header including kibana version with agent', async () => { mockRegistryProxyUrl = 'url'; - getResponse(''); + await getResponse(''); expect(fetchMock).toHaveBeenCalledWith('', { agent: 'proxy agent', diff --git a/x-pack/plugins/fleet/server/services/files/index.test.ts b/x-pack/plugins/fleet/server/services/files/index.test.ts index 551cded5d66b2..2991c1dcbd1a6 100644 --- a/x-pack/plugins/fleet/server/services/files/index.test.ts +++ b/x-pack/plugins/fleet/server/services/files/index.test.ts @@ -165,14 +165,14 @@ describe('files service', () => { }); describe('#updateFilesStatus()', () => { - it('calls esClient.updateByQuery with expected values', () => { + it('calls esClient.updateByQuery with expected values', async () => { const FAKE_INTEGRATION_METADATA_INDEX = getFileMetadataIndexName('someintegration'); const files = { [ENDPOINT_FILE_METADATA_INDEX]: new Set(['delete1', 'delete2']), [FAKE_INTEGRATION_METADATA_INDEX]: new Set(['delete2', 'delete3']), }; const status = 'DELETED'; - updateFilesStatus(esClientMock, abortController, files, status); + await updateFilesStatus(esClientMock, abortController, files, status); expect(esClientMock.updateByQuery).toHaveBeenNthCalledWith( 1, diff --git a/x-pack/plugins/fleet/server/services/fleet_proxies.ts b/x-pack/plugins/fleet/server/services/fleet_proxies.ts index 61aa07b8e0614..4cfc81db50740 100644 --- a/x-pack/plugins/fleet/server/services/fleet_proxies.ts +++ b/x-pack/plugins/fleet/server/services/fleet_proxies.ts @@ -201,32 +201,30 @@ async function updateRelatedSavedObject( ) { await pMap( fleetServerHosts, - (fleetServerHost) => { + (fleetServerHost) => updateFleetServerHost(soClient, fleetServerHost.id, { ...omit(fleetServerHost, 'id'), proxy_id: null, - }); - }, + }), { concurrency: 20 } ); await pMap( outputs, - (output) => { + (output) => outputService.update(soClient, esClient, output.id, { ...omit(output, 'id'), proxy_id: null, - } as Partial); - }, + } as Partial), { concurrency: 20 } ); - await pMap(downloadSources, (downloadSource) => { + await pMap(downloadSources, (downloadSource) => downloadSourceService.update(soClient, downloadSource.id, { ...omit(downloadSource, 'id'), proxy_id: null, - }); - }); + }) + ); } export async function getFleetProxyRelatedSavedObjects( diff --git a/x-pack/plugins/fleet/server/services/fleet_usage_logger.ts b/x-pack/plugins/fleet/server/services/fleet_usage_logger.ts index 92a1dc6da33cf..76c4aba77038c 100644 --- a/x-pack/plugins/fleet/server/services/fleet_usage_logger.ts +++ b/x-pack/plugins/fleet/server/services/fleet_usage_logger.ts @@ -17,7 +17,7 @@ import { appContextService } from './app_context'; const TASK_ID = 'Fleet-Usage-Logger-Task'; const TASK_TYPE = 'Fleet-Usage-Logger'; -export async function registerFleetUsageLogger( +export function registerFleetUsageLogger( taskManager: TaskManagerSetupContract, fetchUsage: () => ReturnType ) { diff --git a/x-pack/plugins/fleet/server/services/output.test.ts b/x-pack/plugins/fleet/server/services/output.test.ts index 13409f7e6f1d4..887a0ac9e0c8f 100644 --- a/x-pack/plugins/fleet/server/services/output.test.ts +++ b/x-pack/plugins/fleet/server/services/output.test.ts @@ -522,7 +522,7 @@ describe('Output Service', () => { it('should throw an error when preset: balanced is provided but config_yaml contains a reserved key', async () => { const soClient = getMockedSoClient({}); - expect( + await expect( outputService.create( soClient, esClientMock, @@ -879,7 +879,7 @@ describe('Output Service', () => { defaultOutputId: 'output-test', }); - expect( + await expect( outputService.create( soClient, esClientMock, @@ -1703,7 +1703,7 @@ describe('Output Service', () => { defaultOutputId: 'output-test', }); - expect( + await expect( outputService.update(soClient, esClientMock, 'output-test', { is_default: true, is_default_monitoring: false, diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index cc605900c3a58..9d288049f67df 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -2888,7 +2888,7 @@ describe('Package policy service', () => { it('should fail to return the package policy', async () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - expect( + await expect( packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', newPackagePolicy, @@ -4905,7 +4905,7 @@ describe('Package policy service', () => { it('should return error if package policy newer than package version', async () => { mockPackage('aws'); - expect( + await expect( packagePolicyService.getUpgradePackagePolicyInfo(savedObjectsClient, 'package-policy-id') ).rejects.toEqual( new PackagePolicyIneligibleForUpgradeError( @@ -4917,7 +4917,7 @@ describe('Package policy service', () => { it('should return error if package not installed', async () => { mockPackage('notinstalled'); - expect( + await expect( packagePolicyService.getUpgradePackagePolicyInfo(savedObjectsClient, 'package-policy-id') ).rejects.toEqual(new FleetError('Package notinstalled is not installed')); }); diff --git a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts index 741b9e33dec88..9e3ec0f0bbc21 100644 --- a/x-pack/plugins/fleet/server/services/security/message_signing_service.ts +++ b/x-pack/plugins/fleet/server/services/security/message_signing_service.ts @@ -234,7 +234,7 @@ export class MessageSigningService implements MessageSigningServiceInterface { soDoc = result.saved_objects[0]; break; } - finder.close(); + await finder.close(); if (soDoc?.error) { throw soDoc.error; diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 7b9da34315418..63753401cc417 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -352,7 +352,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { tokenObjects = result.saved_objects; break; } - tokensFinder.close(); + await tokensFinder.close(); return tokenObjects; } @@ -497,7 +497,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { if (force) { const config = appContextService.getConfig(); const batchSize = config?.setup?.agentPolicySchemaUpgradeBatchSize ?? 100; - asyncForEach( + await asyncForEach( chunk(policyIds, batchSize), async (policyIdsBatch) => await agentPolicyService.deployPolicies(this.soClient, policyIdsBatch) diff --git a/x-pack/plugins/fleet/server/telemetry/sender.test.ts b/x-pack/plugins/fleet/server/telemetry/sender.test.ts index 74e3e1dba1319..0f79cd8464e3e 100644 --- a/x-pack/plugins/fleet/server/telemetry/sender.test.ts +++ b/x-pack/plugins/fleet/server/telemetry/sender.test.ts @@ -29,7 +29,7 @@ describe('TelemetryEventsSender', () => { let logger: ReturnType; let sender: TelemetryEventsSender; - beforeEach(() => { + beforeEach(async () => { logger = loggingSystemMock.createLogger(); sender = new TelemetryEventsSender(logger); sender['fetchClusterInfo'] = jest.fn(async () => { @@ -41,7 +41,7 @@ describe('TelemetryEventsSender', () => { }, } as InfoResponse; }); - sender.start(undefined, { + await sender.start(undefined, { elasticsearch: { client: { asInternalUser: { info: jest.fn(async () => ({})) } } }, } as any); }); diff --git a/x-pack/plugins/fleet/server/telemetry/sender.ts b/x-pack/plugins/fleet/server/telemetry/sender.ts index 7684284165ee1..d50464b1a34e1 100644 --- a/x-pack/plugins/fleet/server/telemetry/sender.ts +++ b/x-pack/plugins/fleet/server/telemetry/sender.ts @@ -14,6 +14,8 @@ import axios from 'axios'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; +import { exhaustMap, Subject, takeUntil, timer } from 'rxjs'; + import { TelemetryQueue } from './queue'; import type { FleetTelemetryChannel, FleetTelemetryChannelEvents } from './types'; @@ -26,10 +28,10 @@ export class TelemetryEventsSender { private readonly initialCheckDelayMs = 10 * 1000; private readonly checkIntervalMs = 30 * 1000; private readonly logger: Logger; + private readonly stop$ = new Subject(); private telemetryStart?: TelemetryPluginStart; private telemetrySetup?: TelemetryPluginSetup; - private intervalId?: NodeJS.Timeout; private isSending = false; private queuesPerChannel: { [channel: string]: TelemetryQueue } = {}; private isOptedIn?: boolean = true; // Assume true until the first check @@ -50,16 +52,16 @@ export class TelemetryEventsSender { this.clusterInfo = await this.fetchClusterInfo(); this.logger.debug(`Starting local task`); - setTimeout(() => { - this.sendIfDue(); - this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); - }, this.initialCheckDelayMs); + timer(this.initialCheckDelayMs, this.checkIntervalMs) + .pipe( + takeUntil(this.stop$), + exhaustMap(() => this.sendIfDue()) + ) + .subscribe(); } public stop() { - if (this.intervalId) { - clearInterval(this.intervalId); - } + this.stop$.next(); } public queueTelemetryEvents( diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index e622a7e7e7023..99fa4075d2d94 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -84,6 +84,7 @@ const appDependencies = { enableIndexStats: true, editableIndexSettings: 'all', enableDataStreamsStorageColumn: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, } as any; diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 460044312d23a..8ee80e2f8f55f 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -62,6 +62,7 @@ export interface AppDependencies { enableIndexStats: boolean; editableIndexSettings: 'all' | 'limited'; enableDataStreamsStorageColumn: boolean; + enableMappingsSourceFieldSection: boolean; enableTogglingDataRetention: boolean; }; history: ScopedHistory; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx new file mode 100644 index 0000000000000..4c73fd1037dda --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AppDependencies } from '../../../..'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import { ConfigurationForm } from '../../components/configuration_form'; +import { WithAppDependencies } from './helpers/setup_environment'; +import { TestSubjects } from './helpers/mappings_editor.helpers'; +import { act } from 'react-dom/test-utils'; + +const setup = (props: any = { onUpdate() {} }, appDependencies?: any) => { + const setupTestBed = registerTestBed( + WithAppDependencies(ConfigurationForm, appDependencies), + { + memoryRouter: { + wrapComponent: false, + }, + defaultProps: props, + } + ); + + const testBed = setupTestBed(); + + return testBed; +}; + +describe('Mappings editor: configuration form', () => { + let testBed: TestBed; + + it('renders the form', async () => { + const ctx = { + config: { + enableMappingsSourceFieldSection: true, + }, + } as unknown as AppDependencies; + + await act(async () => { + testBed = setup({ esNodesPlugins: [] }, ctx); + }); + testBed.component.update(); + const { exists } = testBed; + + expect(exists('advancedConfiguration')).toBe(true); + }); + + describe('_source field', () => { + it('renders the _source field when it is enabled', async () => { + const ctx = { + config: { + enableMappingsSourceFieldSection: true, + }, + } as unknown as AppDependencies; + + await act(async () => { + testBed = setup({ esNodesPlugins: [] }, ctx); + }); + testBed.component.update(); + const { exists } = testBed; + + expect(exists('sourceField')).toBe(true); + }); + + it("doesn't render the _source field when it is disabled", async () => { + const ctx = { + config: { + enableMappingsSourceFieldSection: false, + }, + } as unknown as AppDependencies; + + await act(async () => { + testBed = setup({ esNodesPlugins: [] }, ctx); + }); + testBed.component.update(); + const { exists } = testBed; + + expect(exists('sourceField')).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 2d29d95f32403..e1c3ea7dc6179 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -376,13 +376,19 @@ const createActions = (testBed: TestBed) => { }; }; -export const setup = (props: any = { onUpdate() {} }): MappingsEditorTestBed => { - const setupTestBed = registerTestBed(WithAppDependencies(MappingsEditor), { - memoryRouter: { - wrapComponent: false, - }, - defaultProps: props, - }); +export const setup = ( + props: any = { onUpdate() {} }, + appDependencies?: any +): MappingsEditorTestBed => { + const setupTestBed = registerTestBed( + WithAppDependencies(MappingsEditor, appDependencies), + { + memoryRouter: { + wrapComponent: false, + }, + defaultProps: props, + } + ); const testBed = setupTestBed() as MappingsEditorTestBed; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx index 3bde8c654b165..baeb7dc7b2946 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx @@ -13,6 +13,7 @@ import { docLinksServiceMock, uiSettingsServiceMock } from '@kbn/core/public/moc import { MAJOR_VERSION } from '../../../../../../../common'; import { MappingsEditorProvider } from '../../../mappings_editor_context'; import { createKibanaReactContext } from '../../../shared_imports'; +import { AppContextProvider } from '../../../../../app_context'; import { Props as MappingsEditorProps } from '../../../mappings_editor'; export const kibanaVersion = new SemVer(MAJOR_VERSION); @@ -83,14 +84,16 @@ const defaultProps: MappingsEditorProps = { }; export const WithAppDependencies = - (Comp: MemoExoticComponent>) => + (Comp: MemoExoticComponent>, appDependencies?: any) => (props: Partial) => ( - - - - - + + + + + + + ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 8e06f024666a4..33f97c6f61c8f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -133,9 +133,15 @@ describe('Mappings editor: core', () => { dynamic_templates: [{ before: 'foo' }], }; + const ctx = { + config: { + enableMappingsSourceFieldSection: true, + }, + }; + beforeEach(async () => { await act(async () => { - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }, ctx); }); testBed.component.update(); }); @@ -248,10 +254,13 @@ describe('Mappings editor: core', () => { test('should keep default dynamic templates value when switching tabs', async () => { await act(async () => { - testBed = setup({ - value: { ...defaultMappings, dynamic_templates: [] }, // by default, the UI will provide an empty array for dynamic templates - onChange: onChangeHandler, - }); + testBed = setup( + { + value: { ...defaultMappings, dynamic_templates: [] }, // by default, the UI will provide an empty array for dynamic templates + onChange: onChangeHandler, + }, + ctx + ); }); testBed.component.update(); @@ -282,6 +291,12 @@ describe('Mappings editor: core', () => { */ let defaultMappings: any; + const ctx = { + config: { + enableMappingsSourceFieldSection: true, + }, + }; + beforeEach(async () => { defaultMappings = { dynamic: true, @@ -312,7 +327,7 @@ describe('Mappings editor: core', () => { }; await act(async () => { - testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }, ctx); }); testBed.component.update(); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 4e4c146c85957..571449a5de29e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -5,9 +5,11 @@ * 2.0. */ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; +import { FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { useAppContext } from '../../../../app_context'; import { useForm, Form } from '../../shared_imports'; import { GenericObject, MappingsConfiguration } from '../../types'; import { MapperSizePluginId } from '../../constants'; @@ -25,7 +27,7 @@ interface Props { esNodesPlugins: string[]; } -const formSerializer = (formData: GenericObject) => { +const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { const { dynamicMapping: { enabled: dynamicMappingsEnabled, @@ -49,7 +51,7 @@ const formSerializer = (formData: GenericObject) => { numeric_detection, date_detection, dynamic_date_formats, - _source: sourceField, + _source: sourceFieldMode ? { mode: sourceFieldMode } : sourceField, _meta: metaField, _routing, _size, @@ -97,11 +99,20 @@ const formDeserializer = (formData: GenericObject) => { }; export const ConfigurationForm = React.memo(({ value, esNodesPlugins }: Props) => { + const { + config: { enableMappingsSourceFieldSection }, + } = useAppContext(); + const isMounted = useRef(false); + const serializerCallback = useCallback( + (formData: FormData) => formSerializer(formData, value?._source?.mode), + [value?._source?.mode] + ); + const { form } = useForm({ schema: configurationFormSchema, - serializer: formSerializer, + serializer: serializerCallback, deserializer: formDeserializer, defaultValue: value, id: 'configurationForm', @@ -159,8 +170,11 @@ export const ConfigurationForm = React.memo(({ value, esNodesPlugins }: Props) = - - + {enableMappingsSourceFieldSection && !value?._source?.mode && ( + <> + + + )} {isMapperSizeSectionVisible && } diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts index 3691367e4a8f2..70412e1dc083f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts @@ -223,6 +223,7 @@ export const mappingsConfigurationSchema = t.exact( enabled: t.boolean, includes: t.array(t.string), excludes: t.array(t.string), + mode: t.union([t.literal('disabled'), t.literal('stored'), t.literal('synthetic')]), }) ), _meta: t.UnknownRecord, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts index ff81376b73392..555709008b8dd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts @@ -30,6 +30,7 @@ export interface MappingsConfiguration { enabled?: boolean; includes?: string[]; excludes?: string[]; + mode?: string; }; _meta?: string; _size?: { enabled: boolean }; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 319d6b67c5f1a..4e6947b56ba9e 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -44,6 +44,7 @@ export class IndexMgmtUIPlugin editableIndexSettings: 'all' | 'limited'; enableDataStreamsStorageColumn: boolean; isIndexManagementUiEnabled: boolean; + enableMappingsSourceFieldSection: boolean; enableTogglingDataRetention: boolean; }; @@ -59,6 +60,7 @@ export class IndexMgmtUIPlugin enableIndexStats, editableIndexSettings, enableDataStreamsStorageColumn, + enableMappingsSourceFieldSection, enableTogglingDataRetention, } = ctx.config.get(); this.config = { @@ -68,6 +70,7 @@ export class IndexMgmtUIPlugin enableIndexStats: enableIndexStats ?? true, editableIndexSettings: editableIndexSettings ?? 'all', enableDataStreamsStorageColumn: enableDataStreamsStorageColumn ?? true, + enableMappingsSourceFieldSection: enableMappingsSourceFieldSection ?? true, enableTogglingDataRetention: enableTogglingDataRetention ?? true, }; } diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index e0be3cd891632..6cbca148c4795 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -39,5 +39,6 @@ export interface ClientConfigType { enableIndexStats?: boolean; editableIndexSettings?: 'all' | 'limited'; enableDataStreamsStorageColumn?: boolean; + enableMappingsSourceFieldSection?: boolean; enableTogglingDataRetention?: boolean; } diff --git a/x-pack/plugins/index_management/server/config.ts b/x-pack/plugins/index_management/server/config.ts index d0cb247e0d37a..4348d7ac2a774 100644 --- a/x-pack/plugins/index_management/server/config.ts +++ b/x-pack/plugins/index_management/server/config.ts @@ -52,6 +52,11 @@ const schemaLatest = schema.object( // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana serverless: schema.boolean({ defaultValue: true }), }), + enableMappingsSourceFieldSection: offeringBasedSchema({ + // The _source field in the Mappings editor's advanced options form is disabled in serverless; refer to the serverless.yml file as the source of truth + // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana + serverless: schema.boolean({ defaultValue: true }), + }), enableTogglingDataRetention: offeringBasedSchema({ // The toggle for enabling data retention for DSL in data streams UI is disabled in serverless; refer to the serverless.yml file as the source of truth // We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana @@ -69,6 +74,7 @@ const configLatest: PluginConfigDescriptor = { enableIndexStats: true, editableIndexSettings: true, enableDataStreamsStorageColumn: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, schema: schemaLatest, diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 291e81042f0bc..9d3b4858b0630 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -57,6 +57,7 @@ export class IndexMgmtServerPlugin implements Plugin { isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, isDataStreamsStorageColumnEnabled: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, indexDataEnricher: mockedIndexDataEnricher, @@ -119,6 +120,7 @@ describe('GET privileges', () => { isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, isDataStreamsStorageColumnEnabled: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, indexDataEnricher: mockedIndexDataEnricher, diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts index 9b8f461036708..05ee689871aea 100644 --- a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts @@ -49,6 +49,7 @@ describe('GET privileges', () => { isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, isDataStreamsStorageColumnEnabled: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, indexDataEnricher: mockedIndexDataEnricher, @@ -119,6 +120,7 @@ describe('GET privileges', () => { isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, isDataStreamsStorageColumnEnabled: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, indexDataEnricher: mockedIndexDataEnricher, diff --git a/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts b/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts index b939f374be078..6fbfef5a9528e 100644 --- a/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts +++ b/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts @@ -15,6 +15,7 @@ export const routeDependencies: Omit = { isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, isDataStreamsStorageColumnEnabled: true, + enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, }, indexDataEnricher: new IndexDataEnricher(), diff --git a/x-pack/plugins/index_management/server/types.ts b/x-pack/plugins/index_management/server/types.ts index ee0c1d497766d..1405ed1c3ed93 100644 --- a/x-pack/plugins/index_management/server/types.ts +++ b/x-pack/plugins/index_management/server/types.ts @@ -26,6 +26,7 @@ export interface RouteDependencies { isLegacyTemplatesEnabled: boolean; isIndexStatsEnabled: boolean; isDataStreamsStorageColumnEnabled: boolean; + enableMappingsSourceFieldSection: boolean; enableTogglingDataRetention: boolean; }; indexDataEnricher: IndexDataEnricher; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index d4db046665fc7..3206947c2a9b7 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -11,6 +11,7 @@ import type { Observable } from 'rxjs'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { render, unmountComponentAtNode } from 'react-dom'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { DataViewBase, EsQueryConfig, @@ -1556,7 +1557,7 @@ export class Embeddable public getIsEditable() { // for ES|QL, editing is allowed only if the advanced setting is on - if (Boolean(this.isTextBasedLanguage()) && !this.deps.uiSettings.get('discover:enableESQL')) { + if (Boolean(this.isTextBasedLanguage()) && !this.deps.uiSettings.get(ENABLE_ESQL)) { return false; } return ( diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx index 27372f10ce973..97b59a93829e3 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx @@ -32,7 +32,7 @@ describe('create Lens panel action', () => { uiSettings: { ...core.uiSettings, get: (setting: string) => { - return setting === 'discover:enableESQL'; + return setting === 'enableESQL'; }, }, } as CoreStart; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts index 3e77f8979a872..b99583ddf3103 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts @@ -9,7 +9,7 @@ import type { CoreStart } from '@kbn/core/public'; import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { PresentationContainer } from '@kbn/presentation-containers'; -import { getESQLAdHocDataview, getIndexForESQLQuery } from '@kbn/esql-utils'; +import { getESQLAdHocDataview, getIndexForESQLQuery, ENABLE_ESQL } from '@kbn/esql-utils'; import type { Datasource, Visualization } from '../../types'; import type { LensPluginStartDependencies } from '../../plugin'; import { fetchDataFromAggregateQuery } from '../../datasources/text_based/fetch_data_from_aggregate_query'; @@ -28,7 +28,7 @@ export const [getDatasourceMap, setDatasourceMap] = createGetterSetter< >('DatasourceMap', false); export function isCreateActionCompatible(core: CoreStart) { - return core.uiSettings.get('discover:enableESQL'); + return core.uiSettings.get(ENABLE_ESQL); } export async function executeCreateAction({ diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx index a6c7a8bcf6bc0..7525f491e697a 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx @@ -51,7 +51,7 @@ describe('inapp editing of Lens embeddable', () => { uiSettings: { ...core.uiSettings, get: (setting: string) => { - return setting === 'discover:enableESQL'; + return setting === 'enableESQL'; }, }, } as CoreStart; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx index 168dbebe3ebd2..0a3dc8feebe0b 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx @@ -8,6 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import type { CoreStart } from '@kbn/core/public'; import { isOfAggregateQueryType } from '@kbn/es-query'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import type { LensPluginStartDependencies } from '../../../plugin'; @@ -21,7 +22,7 @@ export function isEmbeddableEditActionCompatible( ) { // for ES|QL is compatible only when advanced setting is enabled const query = attributes.state.query; - return isOfAggregateQueryType(query) ? core.uiSettings.get('discover:enableESQL') : true; + return isOfAggregateQueryType(query) ? core.uiSettings.get(ENABLE_ESQL) : true; } export async function executeEditEmbeddableAction({ diff --git a/x-pack/plugins/license_api_guard/server/license.test.ts b/x-pack/plugins/license_api_guard/server/license.test.ts index 9f20a3e20e995..8beeddfd1d35c 100644 --- a/x-pack/plugins/license_api_guard/server/license.test.ts +++ b/x-pack/plugins/license_api_guard/server/license.test.ts @@ -58,7 +58,14 @@ describe('License API guard', () => { const forbidden = jest.fn(); const responseMock = httpServerMock.createResponseFactory(); responseMock.forbidden = forbidden; - guardedRoute({} as RequestHandlerContext, {} as KibanaRequest, responseMock); + const maybePromise = guardedRoute( + {} as RequestHandlerContext, + {} as KibanaRequest, + responseMock + ); + if (maybePromise instanceof Promise) { + maybePromise.catch(() => {}); + } return { errorResponse: diff --git a/x-pack/plugins/license_management/server/plugin.ts b/x-pack/plugins/license_management/server/plugin.ts index 6368d973a10eb..e1859f84fd162 100644 --- a/x-pack/plugins/license_management/server/plugin.ts +++ b/x-pack/plugins/license_management/server/plugin.ts @@ -35,7 +35,7 @@ export class LicenseManagementServerPlugin ], }); - getStartServices().then(([, { licensing }]) => { + void getStartServices().then(([, { licensing }]) => { this.apiRoutes.setup({ router, plugins: { diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items_point_in_time_finder.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items_point_in_time_finder.ts index 8bd79eaa7f3a2..0f9188930c44b 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items_point_in_time_finder.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_items_point_in_time_finder.ts @@ -118,7 +118,7 @@ export const findExceptionListsItemPointInTimeFinder = async ({ ); executeFunctionOnStream(exceptionListItem); try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal @@ -132,7 +132,7 @@ export const findExceptionListsItemPointInTimeFinder = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_point_in_time_finder.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_point_in_time_finder.ts index 77869f2f847fa..4fc9bf96d2a7d 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_point_in_time_finder.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_point_in_time_finder.ts @@ -96,7 +96,7 @@ export const findExceptionListPointInTimeFinder = async ({ exceptionList.data = exceptionList.data.slice(-exceptionList.data.length, -diff); executeFunctionOnStream(exceptionList); try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal @@ -109,7 +109,7 @@ export const findExceptionListPointInTimeFinder = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_value_list_exception_list_items_point_in_time_finder.ts b/x-pack/plugins/lists/server/services/exception_lists/find_value_list_exception_list_items_point_in_time_finder.ts index bc34bb18162f7..b216d02437e2a 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/find_value_list_exception_list_items_point_in_time_finder.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/find_value_list_exception_list_items_point_in_time_finder.ts @@ -95,7 +95,7 @@ export const findValueListExceptionListItemsPointInTimeFinder = async ({ exceptionList.data = exceptionList.data.slice(-exceptionList.data.length, -diff); executeFunctionOnStream(exceptionList); try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal @@ -108,7 +108,7 @@ export const findValueListExceptionListItemsPointInTimeFinder = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index eea440b8b2afc..976a7ec4da084 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -8,6 +8,8 @@ import type { LayerDescriptor } from '../../common/descriptor_types'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; import type { SampleValuesConfig, EMSTermJoinConfig } from '../ems_autosuggest'; +import type { Props as PassiveMapProps } from '../lens/passive_map'; +import type { Props as MapProps } from '../embeddable/map_component'; export interface MapsStartApi { createLayerDescriptors: { @@ -20,5 +22,7 @@ export interface MapsStartApi { params: CreateLayerDescriptorParams ) => Promise; }; + Map: React.FC; + PassiveMap: React.FC; suggestEMSTermJoinConfig(config: SampleValuesConfig): Promise; } diff --git a/x-pack/plugins/maps/public/embeddable/map_component.tsx b/x-pack/plugins/maps/public/embeddable/map_component.tsx index 7d1a3ed734737..a9f3fe4afbc0b 100644 --- a/x-pack/plugins/maps/public/embeddable/map_component.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_component.tsx @@ -8,19 +8,21 @@ import React, { Component, RefObject } from 'react'; import { first } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; -import type { Filter } from '@kbn/es-query'; -import type { Query, TimeRange } from '@kbn/es-query'; -import type { LayerDescriptor, MapCenterAndZoom } from '../../common/descriptor_types'; -import type { MapEmbeddableType } from './types'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../common/descriptor_types'; import { MapEmbeddable } from './map_embeddable'; import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -interface Props { - title: string; +export interface Props { + title?: string; filters?: Filter[]; query?: Query; timeRange?: TimeRange; - getLayerDescriptors: () => LayerDescriptor[]; + layerList: LayerDescriptor[]; + mapSettings?: Partial; + hideFilterActions?: boolean; + isLayerTOCOpen?: boolean; mapCenter?: MapCenterAndZoom; onInitialRenderComplete?: () => void; /* @@ -30,11 +32,13 @@ interface Props { } export class MapComponent extends Component { - private _mapEmbeddable: MapEmbeddableType; + private _prevLayerList: LayerDescriptor[]; + private _mapEmbeddable: MapEmbeddable; private readonly _embeddableRef: RefObject = React.createRef(); constructor(props: Props) { super(props); + this._prevLayerList = this.props.layerList; this._mapEmbeddable = new MapEmbeddable( { editable: false, @@ -42,15 +46,24 @@ export class MapComponent extends Component { { id: uuidv4(), attributes: { - title: this.props.title, - layerListJSON: JSON.stringify([ - createBasemapLayerDescriptor(), - ...this.props.getLayerDescriptors(), - ]), + title: this.props.title ?? '', + layerListJSON: JSON.stringify(this.getLayerList()), }, + hidePanelTitles: !Boolean(this.props.title), + viewMode: ViewMode.VIEW, + isLayerTOCOpen: + typeof this.props.isLayerTOCOpen === 'boolean' ? this.props.isLayerTOCOpen : false, + hideFilterActions: + typeof this.props.hideFilterActions === 'boolean' ? this.props.hideFilterActions : false, mapCenter: this.props.mapCenter, + mapSettings: this.props.mapSettings ?? {}, } ); + this._mapEmbeddable.updateInput({ + filters: this.props.filters, + query: this.props.query, + timeRange: this.props.timeRange, + }); if (this.props.onInitialRenderComplete) { this._mapEmbeddable @@ -84,6 +97,16 @@ export class MapComponent extends Component { query: this.props.query, timeRange: this.props.timeRange, }); + + if (this._prevLayerList !== this.props.layerList) { + this._mapEmbeddable.setLayerList(this.getLayerList()); + this._prevLayerList = this.props.layerList; + } + } + + getLayerList(): LayerDescriptor[] { + const basemapLayer = createBasemapLayerDescriptor(); + return basemapLayer ? [basemapLayer, ...this.props.layerList] : this.props.layerList; } render() { diff --git a/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx b/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx new file mode 100644 index 0000000000000..2fff281a23ffd --- /dev/null +++ b/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { Props } from './map_component'; + +const Component = dynamic(async () => { + const { MapComponent } = await import('./map_component'); + return { + default: MapComponent, + }; +}); + +export function MapComponentLazy(props: Props) { + return ; +} diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx index 0baa70ead96e4..66c623bf524a7 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { lazy } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { RegionMapVisRenderValue } from './region_map_fn'; -import { LazyWrapper } from '../../lazy_wrapper'; import { REGION_MAP_RENDER } from './types'; -const getLazyComponent = () => { - return lazy(() => import('./region_map_visualization')); -}; +const Component = dynamic(async () => { + const { RegionMapVisualization } = await import('./region_map_visualization'); + return { + default: RegionMapVisualization, + }; +}); export const regionMapRenderer = { name: REGION_MAP_RENDER, @@ -34,6 +37,6 @@ export const regionMapRenderer = { visConfig, }; - render(, domNode); + render(, domNode); }, } as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx index 4dedb97202857..701b7baf002b2 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import { RegionMapVisConfig } from './types'; @@ -20,30 +20,33 @@ interface Props { onInitialRenderComplete: () => void; } -function RegionMapVisualization(props: Props) { - const mapCenter = { - lat: props.visConfig.mapCenter[0], - lon: props.visConfig.mapCenter[1], - zoom: props.visConfig.mapZoom, - }; - function getLayerDescriptors() { +export function RegionMapVisualization(props: Props) { + const initialMapCenter = useMemo(() => { + return { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const initialLayerList = useMemo(() => { const layerDescriptor = createRegionMapLayerDescriptor(props.visConfig.layerDescriptorParams); return layerDescriptor ? [layerDescriptor] : []; - } + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( ); } - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export default RegionMapVisualization; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx index 233bd9350e2e7..e784cf340f420 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx @@ -5,16 +5,19 @@ * 2.0. */ -import React, { lazy } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { TileMapVisRenderValue } from './tile_map_fn'; -import { LazyWrapper } from '../../lazy_wrapper'; import { TILE_MAP_RENDER } from './types'; -const getLazyComponent = () => { - return lazy(() => import('./tile_map_visualization')); -}; +const Component = dynamic(async () => { + const { TileMapVisualization } = await import('./tile_map_visualization'); + return { + default: TileMapVisualization, + }; +}); export const tileMapRenderer = { name: TILE_MAP_RENDER, @@ -34,6 +37,6 @@ export const tileMapRenderer = { visConfig, }; - render(, domNode); + render(, domNode); }, } as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx index 5eb4132528de5..f8f9c74360e8c 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import type { TileMapVisConfig } from './types'; @@ -20,30 +20,33 @@ interface Props { onInitialRenderComplete: () => void; } -function TileMapVisualization(props: Props) { - const mapCenter = { - lat: props.visConfig.mapCenter[0], - lon: props.visConfig.mapCenter[1], - zoom: props.visConfig.mapZoom, - }; - function getLayerDescriptors() { +export function TileMapVisualization(props: Props) { + const initialMapCenter = useMemo(() => { + return { + lat: props.visConfig.mapCenter[0], + lon: props.visConfig.mapCenter[1], + zoom: props.visConfig.mapZoom, + }; + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const initialLayerList = useMemo(() => { const layerDescriptor = createTileMapLayerDescriptor(props.visConfig.layerDescriptorParams); return layerDescriptor ? [layerDescriptor] : []; - } + // props.visConfig reference changes each render but values are the same + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( ); } - -// default export required for React.Lazy -// eslint-disable-next-line import/no-default-export -export default TileMapVisualization; diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx index 94fb1adc2dfd6..7c0613f1a7fa1 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/choropleth_chart.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { FileLayer } from '@elastic/ems-client'; import { IUiSettingsClient } from '@kbn/core/public'; -import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; import type { Datatable } from '@kbn/expressions-plugin/public'; import type { FormatFactory } from '@kbn/field-formats-plugin/common'; import { @@ -24,13 +23,11 @@ import { emsWorldLayerId } from '../../../common/constants'; import { ChoroplethChartProps } from './types'; import { getEmsSuggestion } from './get_ems_suggestion'; import { PassiveMap } from '../passive_map'; -import type { MapEmbeddableInput, MapEmbeddableOutput } from '../../embeddable'; interface Props extends ChoroplethChartProps { formatFactory: FormatFactory; uiSettings: IUiSettingsClient; emsFileLayers: FileLayer[]; - mapEmbeddableFactory: EmbeddableFactory; onRenderComplete: () => void; } @@ -40,7 +37,6 @@ export function ChoroplethChart({ formatFactory, uiSettings, emsFileLayers, - mapEmbeddableFactory, onRenderComplete, }: Props) { if (!args.regionAccessor || !args.valueAccessor) { @@ -130,13 +126,7 @@ export function ChoroplethChart({ type: LAYER_TYPE.GEOJSON_VECTOR, }; - return ( - - ); + return ; } function getAccessorLabel(table: Datatable, accessor: string) { diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx index 059d612883d82..5bb15ed1a7c35 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/expression_renderer.tsx @@ -8,7 +8,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import type { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; -import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; import type { CoreSetup, CoreStart } from '@kbn/core/public'; import type { FileLayer } from '@elastic/ems-client'; @@ -16,7 +15,6 @@ import type { KibanaExecutionContext } from '@kbn/core-execution-context-common' import { ChartSizeEvent } from '@kbn/chart-expressions-common'; import type { MapsPluginStartDependencies } from '../../plugin'; import type { ChoroplethChartProps } from './types'; -import type { MapEmbeddableInput, MapEmbeddableOutput } from '../../embeddable'; export const RENDERER_ID = 'lens_choropleth_chart_renderer'; @@ -65,13 +63,6 @@ export function getExpressionRenderer(coreSetup: CoreSetup; - if (!mapEmbeddableFactory) { - return; - } - let emsFileLayers: FileLayer[] = []; try { emsFileLayers = await getEmsFileLayers(); @@ -111,7 +102,6 @@ export function getExpressionRenderer(coreSetup: CoreSetup, domNode diff --git a/x-pack/plugins/maps/public/lens/index.ts b/x-pack/plugins/maps/public/lens/index.ts index 1af581ca196f0..744153ba5e736 100644 --- a/x-pack/plugins/maps/public/lens/index.ts +++ b/x-pack/plugins/maps/public/lens/index.ts @@ -6,3 +6,4 @@ */ export { setupLensChoroplethChart } from './choropleth_chart'; +export { PassiveMapLazy } from './passive_map_lazy'; diff --git a/x-pack/plugins/maps/public/lens/passive_map.tsx b/x-pack/plugins/maps/public/lens/passive_map.tsx index 06450ee16f501..31e90ee8e91f8 100644 --- a/x-pack/plugins/maps/public/lens/passive_map.tsx +++ b/x-pack/plugins/maps/public/lens/passive_map.tsx @@ -9,16 +9,15 @@ import React, { Component, RefObject } from 'react'; import { Subscription } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { EuiLoadingChart } from '@elastic/eui'; -import { EmbeddableFactory, ViewMode } from '@kbn/embeddable-plugin/public'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; import type { LayerDescriptor } from '../../common/descriptor_types'; import { INITIAL_LOCATION } from '../../common'; -import { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from '../embeddable'; +import { MapEmbeddable } from '../embeddable'; import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -interface Props { - factory: EmbeddableFactory; +export interface Props { passiveLayer: LayerDescriptor; - onRenderComplete: () => void; + onRenderComplete?: () => void; } interface State { @@ -65,37 +64,40 @@ export class PassiveMap extends Component { async _setupEmbeddable() { const basemapLayerDescriptor = createBasemapLayerDescriptor(); const intialLayers = basemapLayerDescriptor ? [basemapLayerDescriptor] : []; - const mapEmbeddable = (await this.props.factory.create({ - id: uuidv4(), - attributes: { - title: '', - layerListJSON: JSON.stringify([...intialLayers, this.props.passiveLayer]), + const mapEmbeddable = new MapEmbeddable( + { + editable: false, }, - filters: [], - hidePanelTitles: true, - viewMode: ViewMode.VIEW, - isLayerTOCOpen: false, - hideFilterActions: true, - mapSettings: { - disableInteractive: false, - hideToolbarOverlay: false, - hideLayerControl: false, - hideViewControl: false, - initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent - autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query - }, - })) as MapEmbeddable | undefined; + { + id: uuidv4(), + attributes: { + title: '', + layerListJSON: JSON.stringify([...intialLayers, this.props.passiveLayer]), + }, + filters: [], + hidePanelTitles: true, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + } + ); - if (!mapEmbeddable) { - return; + if (this.props.onRenderComplete) { + this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => { + if (this._isMounted && this.props.onRenderComplete) { + this.props.onRenderComplete(); + } + }); } - this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => { - if (this._isMounted) { - this.props.onRenderComplete(); - } - }); - if (this._isMounted) { mapEmbeddable.setIsSharable(false); this.setState({ mapEmbeddable }, () => { diff --git a/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx b/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx new file mode 100644 index 0000000000000..5929f77a5f943 --- /dev/null +++ b/x-pack/plugins/maps/public/lens/passive_map_lazy.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { Props } from './passive_map'; + +const Component = dynamic(async () => { + const { PassiveMap } = await import('./passive_map'); + return { + default: PassiveMap, + }; +}); + +export function PassiveMapLazy(props: Props) { + return ; +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 4fb9bc6cd231d..2eaabaa00aaf6 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -90,10 +90,11 @@ import { import { MapInspectorView } from './inspector/map_adapter/map_inspector_view'; import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_tile_inspector_view'; -import { setupLensChoroplethChart } from './lens'; +import { PassiveMapLazy, setupLensChoroplethChart } from './lens'; import { CONTENT_ID, LATEST_VERSION, MapAttributes } from '../common/content_management'; import { savedObjectToEmbeddableAttributes } from './map_attribute_service'; import { MapByValueInput } from './embeddable'; +import { MapComponentLazy } from './embeddable/map_component_lazy'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; @@ -275,6 +276,8 @@ export class MapsPlugin return { createLayerDescriptors, suggestEMSTermJoinConfig, + Map: MapComponentLazy, + PassiveMap: PassiveMapLazy, }; } } diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 8e725ff81cb0f..0ce9152b016eb 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -87,7 +87,8 @@ "@kbn/presentation-publishing", "@kbn/saved-objects-finder-plugin", "@kbn/esql-utils", - "@kbn/apm-data-view" + "@kbn/apm-data-view", + "@kbn/shared-ux-utility" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 051219481118d..41b2ac3a47d37 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -18,14 +18,12 @@ import { EuiLink, EuiSpacer, EuiText, - EuiBetaBadge, EuiTextAlign, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { ENABLE_ESQL } from '@kbn/discover-utils'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { isFullLicense } from '../license'; import { useMlKibana, useNavigateToPath } from '../contexts/kibana'; import { HelpMenu } from '../components/help_menu'; @@ -174,25 +172,6 @@ export const DatavisualizerSelector: FC = () => { {' '} - - } - tooltipPosition={'right'} - aria-label={i18n.translate( - 'xpack.ml.datavisualizer.selector.technicalPreviewBadge.ariaLabel', - { - defaultMessage: 'ES|QL is in technical preview.', - } - )} /> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 3b3e673f3c59c..157aa89522d75 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -16,7 +16,7 @@ import type { GetAdditionalLinksParams, } from '@kbn/data-visualizer-plugin/public'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import { useMlKibana, useMlLocator } from '../../contexts/kibana'; import { HelpMenu } from '../../components/help_menu'; @@ -26,7 +26,6 @@ import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_ import { checkPermission } from '../../capabilities/check_capabilities'; import { MlPageHeader } from '../../components/page_header'; import { useEnabledFeatures } from '../../contexts/ml'; -import { TechnicalPreviewBadge } from '../../components/technical_preview_badge/technical_preview_badge'; export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) => { useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); const { @@ -188,8 +187,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) // eslint-disable-next-line react-hooks/exhaustive-deps [mlLocator, mlFeaturesDisabled] ); - const { euiTheme } = useEuiTheme(); - return IndexDataVisualizer ? ( {IndexDataVisualizer !== null ? ( @@ -205,9 +202,6 @@ export const IndexDataVisualizerPage: FC<{ esql: boolean }> = ({ esql = false }) - - - ) : null} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index d587f83a50b5e..8870f04f498d0 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -104,6 +104,14 @@ function extractMML(job, newJobData) { if (mml !== job.analysis_limits.model_memory_limit) { mmlData.analysis_limits = { model_memory_limit: mml, + // work around for issue in es where categorization_examples_limit will be reset to the default value + // if it is not included in the update request + // https://github.com/elastic/elasticsearch/issues/108068 + ...(job.analysis_limits.categorization_examples_limit !== undefined + ? { + categorization_examples_limit: job.analysis_limits.categorization_examples_limit, + } + : {}), }; } } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 75eed3900950c..c7eda482aafad 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -21,6 +21,7 @@ import { } from '@kbn/ml-anomaly-utils'; import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { IndexPatternTitle } from '../../../../../../common/types/kibana'; import { getQueryFromSavedSearchObject } from '../../../../util/index_utils'; import type { @@ -327,11 +328,18 @@ export class JobCreator { public set modelMemoryLimit(mml: string | null) { if (mml !== null) { - this._job_config.analysis_limits = { - model_memory_limit: mml, - }; + if (this._job_config.analysis_limits === undefined) { + this._job_config.analysis_limits = {}; + } + this._job_config.analysis_limits.model_memory_limit = mml; } else { - delete this._job_config.analysis_limits; + if (this._job_config.analysis_limits !== undefined) { + delete this._job_config.analysis_limits.model_memory_limit; + + if (isPopulatedObject(this._job_config.analysis_limits) === false) { + delete this._job_config.analysis_limits; + } + } } } diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 8569b318c3b1b..e3f5bd0a3792c 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -463,9 +463,8 @@ export class TimeSeriesExplorer extends React.Component { if ( // If the user's focus range is not defined (i.e. no 'zoom' parameter restored from the appState URL), // then calculate the default focus range to use - zoom === undefined && - (focusRange === undefined || - this.previousSelectedForecastId !== this.props.selectedForecastId) + zoom === undefined || + focusRange === undefined ) { focusRange = this.mlTimeSeriesExplorer.calculateDefaultFocusRange( autoZoomDuration, @@ -476,10 +475,12 @@ export class TimeSeriesExplorer extends React.Component { this.previousSelectedForecastId = this.props.selectedForecastId; } - this.contextChartSelected({ - from: focusRange[0], - to: focusRange[1], - }); + if (focusRange !== undefined) { + this.contextChartSelected({ + from: focusRange[0], + to: focusRange[1], + }); + } } this.setState(stateUpdate); diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index e3bf7e3520649..d762bb9557268 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -50,7 +50,7 @@ import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/publ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; -import { ENABLE_ESQL } from '@kbn/discover-utils'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import type { MlSharedServices } from './application/services/get_shared_ml_services'; import { getMlSharedServices } from './application/services/get_shared_ml_services'; import { registerManagementSection } from './application/management'; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 635638d51f303..93218b6370e80 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -590,7 +590,7 @@ export class DataRecognizer { } } // merge all the save results - this._updateResults(results, saveResults); + await this._updateResults(results, saveResults); return results; } diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts index 8889f6b9572d1..b72ad7d1a4fed 100644 --- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -94,7 +94,7 @@ export function modelSnapshotProvider(client: IScopedClusterClient, mlClient: Ml await cm.newCalendar(calendar); } - forceStartDatafeeds( + await forceStartDatafeeds( [datafeedId], +snapshot.model_snapshots[0].latest_record_time_stamp!, end diff --git a/x-pack/plugins/ml/server/models/json_schema_service/json_schema_service.ts b/x-pack/plugins/ml/server/models/json_schema_service/json_schema_service.ts index ca6e2a0b18b48..86cfd6a0ff7d2 100644 --- a/x-pack/plugins/ml/server/models/json_schema_service/json_schema_service.ts +++ b/x-pack/plugins/ml/server/models/json_schema_service/json_schema_service.ts @@ -119,10 +119,12 @@ export class JsonSchemaService { Fs.readFileSync(Path.resolve(__dirname, 'openapi_source.json'), 'utf8') ); - supportedEndpoints.forEach((e) => { - // need to extract schema in order to keep required components - this.extractSchema(e.path, e.method, schema); - }); + await Promise.all( + supportedEndpoints.map(async (e) => { + // need to extract schema in order to keep required components + await this.extractSchema(e.path, e.method, schema); + }) + ); for (const pathName in schema.paths) { if (!schema.paths.hasOwnProperty(pathName)) continue; diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index ee36bd7382843..aa42a3e18143c 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -342,10 +342,16 @@ export class MlServerPlugin this.security, this.spacesPlugin !== undefined ); - initializeJobs().finally(() => { - this.setMlReady(); + initializeJobs() + .catch((err) => { + this.log.debug(`Error initializing jobs`, err); + }) + .finally(() => { + this.setMlReady(); + }); + this.savedObjectsSyncService.scheduleSyncTask(plugins.taskManager, coreStart).catch((err) => { + this.log.debug(`Error scheduling saved objects sync task`, err); }); - this.savedObjectsSyncService.scheduleSyncTask(plugins.taskManager, coreStart); }); } diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index dba33a6043bc8..4b3ae3a3f0beb 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -124,7 +124,7 @@ "@kbn/presentation-containers", "@kbn/presentation-panel-plugin", "@kbn/shared-ux-utility", - "@kbn/discover-utils", "@kbn/react-kibana-context-render", + "@kbn/esql-utils", ], } diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts index 5228eb488b1f7..f166673258848 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/bulk_uploader.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Observable, Subscription } from 'rxjs'; +import { exhaustMap, type Observable, Subject, type Subscription, takeUntil, timer } from 'rxjs'; import { firstValueFrom } from 'rxjs'; import moment from 'moment'; import type { @@ -67,7 +67,7 @@ export class BulkUploader implements IBulkUploader { private kibanaStatusSubscription?: Subscription; private readonly opsMetrics$: Observable; private kibanaStatus: ServiceStatusLevel | null; - private _timer: NodeJS.Timeout | null; + private readonly stop$ = new Subject(); private readonly _interval: number; private readonly config: MonitoringConfig; @@ -86,7 +86,6 @@ export class BulkUploader implements IBulkUploader { this.opsMetrics$ = opsMetrics$; this.config = config; - this._timer = null; this._interval = interval; this._log = log; @@ -108,15 +107,12 @@ export class BulkUploader implements IBulkUploader { this.kibanaStatus = nextStatus.level; }); - if (this._timer) { - clearInterval(this._timer); - } else { - this._fetchAndUpload(esClient); // initial fetch - } - - this._timer = setInterval(() => { - this._fetchAndUpload(esClient); - }, this._interval); + timer(0, this._interval) + .pipe( + takeUntil(this.stop$), + exhaustMap(() => this._fetchAndUpload(esClient)) + ) + .subscribe(); } /* @@ -125,8 +121,7 @@ export class BulkUploader implements IBulkUploader { * @param {String} logPrefix help give context to the reason for stopping */ public stop(logPrefix?: string) { - if (this._timer) clearInterval(this._timer); - this._timer = null; + this.stop$.next(); this.kibanaStatusSubscription?.unsubscribe(); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 99cc38e44c0e4..5219c3441b13e 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -257,7 +257,7 @@ export class MonitoringPlugin stop() { if (this.cluster && this.cluster.close) { - this.cluster.close(); + this.cluster.close().catch(() => {}); } if (this.licenseService && this.licenseService.stop) { this.licenseService.stop(); diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts index 2f0a505ba1f55..b2dc7248945b6 100644 --- a/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts +++ b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts @@ -21,7 +21,7 @@ describe('sendPlainTextEmail()', () => { }); describe('calls the provided ActionsClient#bulkEnqueueExecution() with the appropriate params', () => { - it(`omits the 'relatedSavedObjects' field if no context is provided`, () => { + it(`omits the 'relatedSavedObjects' field if no context is provided`, async () => { const actionsClient = unsecuredActionsClientMock.create(); actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ errors: false, @@ -40,7 +40,7 @@ describe('sendPlainTextEmail()', () => { message: 'With some contents inside.', }; - email.sendPlainTextEmail(payload); + await email.sendPlainTextEmail(payload); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ @@ -55,7 +55,7 @@ describe('sendPlainTextEmail()', () => { ]); }); - it(`populates the 'relatedSavedObjects' field if context is provided`, () => { + it(`populates the 'relatedSavedObjects' field if context is provided`, async () => { const actionsClient = unsecuredActionsClientMock.create(); actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ errors: false, @@ -83,7 +83,7 @@ describe('sendPlainTextEmail()', () => { }, }; - email.sendPlainTextEmail(payload); + await email.sendPlainTextEmail(payload); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ @@ -168,7 +168,7 @@ describe('sendHTMLEmail()', () => { }); describe('calls the provided ActionsClient#bulkEnqueueExecution() with the appropriate params', () => { - it(`omits the 'relatedSavedObjects' field if no context is provided`, () => { + it(`omits the 'relatedSavedObjects' field if no context is provided`, async () => { const actionsClient = unsecuredActionsClientMock.create(); actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ errors: false, @@ -188,7 +188,7 @@ describe('sendHTMLEmail()', () => { messageHTML: 'With some contents inside.', }; - email.sendHTMLEmail(payload); + await email.sendHTMLEmail(payload); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ @@ -204,7 +204,7 @@ describe('sendHTMLEmail()', () => { ]); }); - it(`populates the 'relatedSavedObjects' field if context is provided`, () => { + it(`populates the 'relatedSavedObjects' field if context is provided`, async () => { const actionsClient = unsecuredActionsClientMock.create(); actionsClient.bulkEnqueueExecution.mockResolvedValueOnce({ errors: false, @@ -233,7 +233,7 @@ describe('sendHTMLEmail()', () => { }, }; - email.sendHTMLEmail(payload); + await email.sendHTMLEmail(payload); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ diff --git a/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts b/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts index 0571285a226c9..d24a151863c06 100644 --- a/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts +++ b/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts @@ -66,7 +66,7 @@ describe('LicensedEmailService', () => { const license$ = new Subject(); const email = new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); - email.sendPlainTextEmail(someEmail); + void email.sendPlainTextEmail(someEmail); expect(emailServiceMock.sendPlainTextEmail).not.toHaveBeenCalled(); license$.next(validLicense); @@ -136,7 +136,7 @@ describe('LicensedEmailService', () => { const license$ = new Subject(); const email = new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); - email.sendHTMLEmail(someHTMLEmail); + void email.sendHTMLEmail(someHTMLEmail); expect(emailServiceMock.sendHTMLEmail).not.toHaveBeenCalled(); license$.next(validLicense); diff --git a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/chart_preview/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/chart_preview/index.tsx index 420e26444dbee..8e7cce37b5be4 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/chart_preview/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/chart_preview/index.tsx @@ -80,7 +80,7 @@ export function ChartPreview({ const { yMin, yMax, xMin, xMax } = getDomain(series); const chartDomain = { - max: Math.max(yMax, threshold) * 1.1, // Add 10% headroom. + max: Math.max(yMax === 0 ? 1 : yMax, threshold) * 1.1, // Add 10% headroom. min: Math.min(yMin, threshold) * 0.9, // Add 10% headroom. }; @@ -110,7 +110,6 @@ export function ChartPreview({ data-test-subj="ChartPreview" > { const dateFormat = (uiSettings && uiSettings.get(UI_SETTINGS.DATE_FORMAT)) || DEFAULT_DATE_FORMAT; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/index.ts index e63502451f04c..5c976c8a499a7 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/apm_telemetry/index.ts @@ -110,37 +110,43 @@ export async function createApmTelemetry({ usageCollector.registerCollector(collector); - core.getStartServices().then(async ([_coreStart, pluginsStart]) => { - const { taskManager: taskManagerStart } = pluginsStart as { - taskManager: TaskManagerStartContract; - }; + core + .getStartServices() + .then(async ([_coreStart, pluginsStart]) => { + const { taskManager: taskManagerStart } = pluginsStart as { + taskManager: TaskManagerStartContract; + }; - taskManagerStart.ensureScheduled({ - id: APM_TELEMETRY_TASK_NAME, - taskType: APM_TELEMETRY_TASK_NAME, - schedule: { - interval: '720m', - }, - scope: ['apm'], - params: {}, - state: {}, - }); + await taskManagerStart.ensureScheduled({ + id: APM_TELEMETRY_TASK_NAME, + taskType: APM_TELEMETRY_TASK_NAME, + schedule: { + interval: '720m', + }, + scope: ['apm'], + params: {}, + state: {}, + }); - try { - const currentData = ( - await savedObjectsClient.get(APM_TELEMETRY_SAVED_OBJECT_TYPE, APM_TELEMETRY_SAVED_OBJECT_ID) - ).attributes as { kibanaVersion?: string }; + try { + const currentData = ( + await savedObjectsClient.get( + APM_TELEMETRY_SAVED_OBJECT_TYPE, + APM_TELEMETRY_SAVED_OBJECT_ID + ) + ).attributes as { kibanaVersion?: string }; - if (currentData.kibanaVersion !== kibanaVersion) { - logger.debug( - `Stored telemetry is out of date. Task will run immediately. Stored: ${currentData.kibanaVersion}, expected: ${kibanaVersion}` - ); - await taskManagerStart.runSoon(APM_TELEMETRY_TASK_NAME); - } - } catch (err) { - if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { - logger.warn('Failed to fetch saved telemetry data.'); + if (currentData.kibanaVersion !== kibanaVersion) { + logger.debug( + `Stored telemetry is out of date. Task will run immediately. Stored: ${currentData.kibanaVersion}, expected: ${kibanaVersion}` + ); + await taskManagerStart.runSoon(APM_TELEMETRY_TASK_NAME); + } + } catch (err) { + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + logger.warn('Failed to fetch saved telemetry data.'); + } } - } - }); + }) + .catch(() => {}); } diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts index 058897dedd32e..43b31c588f422 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts @@ -69,7 +69,7 @@ describe('APMEventClient', () => { await new Promise((resolve) => { setTimeout(() => { - incomingRequest.on('abort', () => { + void incomingRequest.on('abort', () => { setTimeout(() => { resolve(undefined); }, 100); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_alerts_client.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_alerts_client.ts index 63b62d183da4c..3c885eef658d5 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_alerts_client.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_alerts_client.ts @@ -12,7 +12,10 @@ import type { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/re export type ApmAlertsClient = Awaited>; -export async function getApmAlertsClient({ plugins, request }: MinimalAPMRouteHandlerResources) { +export async function getApmAlertsClient({ + plugins, + request, +}: Pick) { const ruleRegistryPluginStart = await plugins.ruleRegistry.start(); const alertsClient = await ruleRegistryPluginStart.getRacClientWithRequest(request); const apmAlertsIndices = await alertsClient.getAuthorizedAlertsIndices(['apm']); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts index 3b80c5ede61d3..b756876eb3212 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts @@ -13,12 +13,11 @@ import { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/registe export async function getApmEventClient({ context, params, - config, getApmIndices, request, }: Pick< MinimalAPMRouteHandlerResources, - 'context' | 'params' | 'config' | 'getApmIndices' | 'request' + 'context' | 'params' | 'getApmIndices' | 'request' >): Promise { return withApmSpan('get_apm_event_client', async () => { const coreContext = await context.core; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_ml_client.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_ml_client.ts index 16b19b7ebed4f..b94a1abd67e2a 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_ml_client.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_ml_client.ts @@ -15,7 +15,11 @@ export interface MlClient { modules: MlModules; } -export async function getMlClient({ plugins, context, request }: MinimalAPMRouteHandlerResources) { +export async function getMlClient({ + plugins, + context, + request, +}: Pick) { const [coreContext, licensingContext] = await Promise.all([context.core, context.licensing]); const mlplugin = plugins.ml; diff --git a/x-pack/plugins/observability_solution/apm/server/plugin.ts b/x-pack/plugins/observability_solution/apm/server/plugin.ts index 8c2bbb8695452..2c2392b845415 100644 --- a/x-pack/plugins/observability_solution/apm/server/plugin.ts +++ b/x-pack/plugins/observability_solution/apm/server/plugin.ts @@ -40,6 +40,7 @@ import { createApmSourceMapIndexTemplate } from './routes/source_maps/create_apm import { addApiKeysToEveryPackagePolicyIfMissing } from './routes/fleet/api_keys/add_api_keys_to_policies_if_missing'; import { apmTutorialCustomIntegration } from '../common/tutorial/tutorials'; import { registerAssistantFunctions } from './assistant_functions'; +import { getAlertDetailsContextHandler } from './routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler'; export class APMPlugin implements Plugin @@ -52,7 +53,7 @@ export class APMPlugin } public setup(core: CoreSetup, plugins: APMPluginSetupDependencies) { - this.logger = this.initContext.logger.get(); + const logger = (this.logger = this.initContext.logger.get()); const config$ = this.initContext.config.create(); core.savedObjects.registerType(apmTelemetry); @@ -76,7 +77,7 @@ export class APMPlugin logger: this.logger, kibanaVersion: this.initContext.env.packageInfo.version, isProd: this.initContext.env.mode.prod, - }); + }).catch(() => {}); } plugins.features.registerKibanaFeature(APM_FEATURE); @@ -127,16 +128,18 @@ export class APMPlugin if (currentConfig.serverlessOnboarding && plugins.customIntegrations) { plugins.customIntegrations?.registerCustomIntegration(apmTutorialCustomIntegration); } else { - apmIndicesPromise.then((apmIndices) => { - plugins.home?.tutorials.registerTutorial( - tutorialProvider({ - apmConfig: currentConfig, - apmIndices, - cloud: plugins.cloud, - isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), - }) - ); - }); + apmIndicesPromise + .then((apmIndices) => { + plugins.home?.tutorials.registerTutorial( + tutorialProvider({ + apmConfig: currentConfig, + apmIndices, + cloud: plugins.cloud, + isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), + }) + ); + }) + .catch(() => {}); } const telemetryUsageCounter = @@ -219,6 +222,10 @@ export class APMPlugin }) ); + plugins.observability.alertDetailsContextualInsightsService.registerHandler( + getAlertDetailsContextHandler(resourcePlugins, logger) + ); + return { config$ }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts index 682976f9120cb..9366e3f04abbb 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts @@ -63,21 +63,21 @@ export async function getTransactionErrorCountChartPreview({ }; const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: interval, - extended_bounds: { - min: start, - max: end, - }, + series: { + multi_terms: { + terms: getGroupByTerms(allGroupByFields), + size: 1000, + order: { _count: 'desc' as const }, }, aggs: { - series: { - multi_terms: { - terms: getGroupByTerms(allGroupByFields), - size: 1000, - order: { _count: 'desc' as const }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: interval, + extended_bounds: { + min: start, + max: end, + }, }, }, }, @@ -95,11 +95,11 @@ export async function getTransactionErrorCountChartPreview({ return { series: [], totalGroups: 0 }; } - const seriesDataMap = resp.aggregations.timeseries.buckets.reduce((acc, bucket) => { - const x = bucket.key; - bucket.series.buckets.forEach((seriesBucket) => { - const bucketKey = seriesBucket.key.join('_'); - const y = seriesBucket.doc_count; + const seriesDataMap = resp.aggregations.series.buckets.reduce((acc, bucket) => { + const bucketKey = bucket.key.join('_'); + bucket.timeseries.buckets.forEach((timeseriesBucket) => { + const x = timeseriesBucket.key; + const y = timeseriesBucket.doc_count; if (acc[bucketKey]) { acc[bucketKey].push({ x, y }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts index a13217f4ca136..0dcf1e3255bd7 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts @@ -90,21 +90,21 @@ export async function getTransactionDurationChartPreview({ const allGroupByFields = getAllGroupByFields(ApmRuleType.TransactionDuration, groupByFields); const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: interval, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, - }, + series: { + multi_terms: { + terms: getGroupByTerms(allGroupByFields), + size: 1000, }, aggs: { - series: { - multi_terms: { - terms: [...getGroupByTerms(allGroupByFields)], - size: 1000, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: interval, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, ...getMultiTermsSortOrder(aggregationType), }, aggs: { @@ -130,14 +130,14 @@ export async function getTransactionDurationChartPreview({ return { series: [], totalGroups: 0 }; } - const seriesDataMap = resp.aggregations.timeseries.buckets.reduce((acc, bucket) => { - const x = bucket.key; - bucket.series.buckets.forEach((seriesBucket) => { - const bucketKey = seriesBucket.key.join('_'); + const seriesDataMap = resp.aggregations.series.buckets.reduce((acc, bucket) => { + const bucketKey = bucket.key.join('_'); + bucket.timeseries.buckets.forEach((timeseriesBucket) => { + const x = timeseriesBucket.key; const y = - 'avgLatency' in seriesBucket - ? seriesBucket.avgLatency.value - : seriesBucket.pctLatency.values[0].value; + 'avgLatency' in timeseriesBucket + ? timeseriesBucket.avgLatency.value + : timeseriesBucket.pctLatency.values[0].value; if (acc[bucketKey]) { acc[bucketKey].push({ x, y }); } else { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts index d9f5c11b1cf14..e6fdf321af8d9 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts @@ -97,21 +97,21 @@ export async function getTransactionErrorRateChartPreview({ }, }, aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: interval, - extended_bounds: { - min: start, - max: end, - }, + series: { + multi_terms: { + terms: getGroupByTerms(allGroupByFields), + size: 1000, + order: { _count: 'desc' as const }, }, aggs: { - series: { - multi_terms: { - terms: [...getGroupByTerms(allGroupByFields)], - size: 1000, - order: { _count: 'desc' as const }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: interval, + extended_bounds: { + min: start, + max: end, + }, }, aggs: { outcomes: { @@ -133,11 +133,11 @@ export async function getTransactionErrorRateChartPreview({ return { series: [], totalGroups: 0 }; } - const seriesDataMap = resp.aggregations.timeseries.buckets.reduce((acc, bucket) => { - const x = bucket.key; - bucket.series.buckets.forEach((seriesBucket) => { - const bucketKey = seriesBucket.key.join('_'); - const y = calculateErrorRate(seriesBucket.outcomes.buckets); + const seriesDataMap = resp.aggregations.series.buckets.reduce((acc, bucket) => { + const bucketKey = bucket.key.join('_'); + bucket.timeseries.buckets.forEach((timeseriesBucket) => { + const x = timeseriesBucket.key; + const y = calculateErrorRate(timeseriesBucket.outcomes.buckets); if (acc[bucketKey]) { acc[bucketKey].push({ x, y }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts new file mode 100644 index 0000000000000..2ffbdc30a1c52 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_changepoints/index.ts @@ -0,0 +1,146 @@ +/* + * 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 { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { ApmTimeseriesType, getApmTimeseries, TimeseriesChangePoint } from '../get_apm_timeseries'; + +export interface ChangePointGrouping { + title: string; + grouping: string; + changes: TimeseriesChangePoint[]; +} + +export async function getServiceChangePoints({ + apmEventClient, + alertStartedAt, + serviceName, + serviceEnvironment, + transactionType, + transactionName, +}: { + apmEventClient: APMEventClient; + alertStartedAt: string; + serviceName: string | undefined; + serviceEnvironment: string | undefined; + transactionType: string | undefined; + transactionName: string | undefined; +}): Promise { + if (!serviceName) { + return []; + } + + const res = await getApmTimeseries({ + apmEventClient, + arguments: { + start: moment(alertStartedAt).subtract(12, 'hours').toISOString(), + end: alertStartedAt, + stats: [ + { + title: 'Latency', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.transactionLatency, + function: LatencyAggregationType.p95, + 'transaction.type': transactionType, + 'transaction.name': transactionName, + }, + }, + { + title: 'Throughput', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.transactionThroughput, + 'transaction.type': transactionType, + 'transaction.name': transactionName, + }, + }, + { + title: 'Failure rate', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.transactionFailureRate, + 'transaction.type': transactionType, + 'transaction.name': transactionName, + }, + }, + { + title: 'Error events', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.errorEventRate, + }, + }, + ], + }, + }); + + return res + .filter((timeseries) => timeseries.changes.length > 0) + .map((timeseries) => ({ + title: timeseries.stat.title, + grouping: timeseries.id, + changes: timeseries.changes, + })); +} + +export async function getExitSpanChangePoints({ + apmEventClient, + alertStartedAt, + serviceName, + serviceEnvironment, +}: { + apmEventClient: APMEventClient; + alertStartedAt: string; + serviceName: string | undefined; + serviceEnvironment: string | undefined; +}): Promise { + if (!serviceName) { + return []; + } + + const res = await getApmTimeseries({ + apmEventClient, + arguments: { + start: moment(alertStartedAt).subtract(30, 'minute').toISOString(), + end: alertStartedAt, + stats: [ + { + title: 'Exit span latency', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.exitSpanLatency, + }, + }, + { + title: 'Exit span failure rate', + 'service.name': serviceName, + 'service.environment': serviceEnvironment, + timeseries: { + name: ApmTimeseriesType.exitSpanFailureRate, + }, + }, + ], + }, + }); + + return res + .filter((timeseries) => timeseries.changes.length > 0) + .map((timeseries) => { + return { + title: timeseries.stat.title, + grouping: timeseries.id, + changes: timeseries.changes, + }; + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts index c842512507bec..990b63f412f76 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts @@ -31,7 +31,7 @@ export async function getLogCategories({ arguments: args, }: { esClient: ElasticsearchClient; - coreContext: CoreRequestHandlerContext; + coreContext: Pick; arguments: { start: string; end: string; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts new file mode 100644 index 0000000000000..cd1a56d56f45e --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_alert_details_context_handler.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/core/server'; +import { + AlertDetailsContextualInsightsHandlerQuery, + AlertDetailsContextualInsightsRequestContext, +} from '@kbn/observability-plugin/server/services'; +import { getApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; +import { getMlClient } from '../../../lib/helpers/get_ml_client'; +import { getRandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { getObservabilityAlertDetailsContext } from '.'; +import { APMRouteHandlerResources } from '../../apm_routes/register_apm_server_routes'; + +export const getAlertDetailsContextHandler = ( + resourcePlugins: APMRouteHandlerResources['plugins'], + logger: Logger +) => { + return async ( + requestContext: AlertDetailsContextualInsightsRequestContext, + query: AlertDetailsContextualInsightsHandlerQuery + ) => { + const resources = { + getApmIndices: async () => { + const coreContext = await requestContext.core; + return resourcePlugins.apmDataAccess.setup.getApmIndices(coreContext.savedObjects.client); + }, + request: requestContext.request, + params: { query: { _inspect: false } }, + plugins: resourcePlugins, + context: { + core: requestContext.core, + licensing: requestContext.licensing, + alerting: resourcePlugins.alerting!.start().then((startContract) => { + return { + getRulesClient() { + return startContract.getRulesClientWithRequest(requestContext.request); + }, + }; + }), + rac: resourcePlugins.ruleRegistry.start().then((startContract) => { + return { + getAlertsClient() { + return startContract.getRacClientWithRequest(requestContext.request); + }, + }; + }), + }, + }; + + const [apmEventClient, annotationsClient, apmAlertsClient, coreContext, mlClient] = + await Promise.all([ + getApmEventClient(resources), + resourcePlugins.observability.setup.getScopedAnnotationsClient( + resources.context, + requestContext.request + ), + getApmAlertsClient(resources), + requestContext.core, + getMlClient(resources), + getRandomSampler({ + security: resourcePlugins.security, + probability: 1, + request: requestContext.request, + }), + ]); + const esClient = coreContext.elasticsearch.client.asCurrentUser; + + return getObservabilityAlertDetailsContext({ + coreContext, + apmEventClient, + annotationsClient, + apmAlertsClient, + mlClient, + esClient, + query, + logger, + }); + }; +}; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts new file mode 100644 index 0000000000000..4a28a0460ebbd --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_apm_alert_details_context_prompt.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import { AlertDetailsContextualInsight } from '@kbn/observability-plugin/server/services'; +import { APMDownstreamDependency } from '../get_apm_downstream_dependencies'; +import { ServiceSummary } from '../get_apm_service_summary'; +import { LogCategories } from '../get_log_categories'; +import { ApmAnomalies } from '../get_apm_service_summary/get_anomalies'; +import { ChangePointGrouping } from '../get_changepoints'; + +export function getApmAlertDetailsContextPrompt({ + serviceName, + serviceEnvironment, + serviceSummary, + downstreamDependencies, + logCategories, + serviceChangePoints, + exitSpanChangePoints, + anomalies, +}: { + serviceName?: string; + serviceEnvironment?: string; + serviceSummary?: ServiceSummary; + downstreamDependencies?: APMDownstreamDependency[]; + logCategories: LogCategories; + serviceChangePoints?: ChangePointGrouping[]; + exitSpanChangePoints?: ChangePointGrouping[]; + anomalies?: ApmAnomalies; +}): AlertDetailsContextualInsight[] { + const prompt: AlertDetailsContextualInsight[] = []; + if (!isEmpty(serviceSummary)) { + prompt.push({ + key: 'serviceSummary', + description: 'Metadata for the service where the alert occurred', + data: serviceSummary, + }); + } + + if (!isEmpty(downstreamDependencies)) { + prompt.push({ + key: 'downstreamDependencies', + description: `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}"`, + data: downstreamDependencies, + }); + } + + if (!isEmpty(serviceChangePoints)) { + prompt.push({ + key: 'serviceChangePoints', + description: `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate`, + data: serviceChangePoints, + }); + } + + if (!isEmpty(exitSpanChangePoints)) { + prompt.push({ + key: 'exitSpanChangePoints', + description: `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies`, + data: exitSpanChangePoints, + }); + } + + if (!isEmpty(logCategories)) { + prompt.push({ + key: 'logCategories', + description: `Log events occurring around the time of the alert`, + data: logCategories, + }); + } + + if (!isEmpty(anomalies)) { + prompt.push({ + key: 'anomalies', + description: `Anomalies for services running in the environment "${serviceEnvironment}"`, + data: anomalies, + }); + } + + return prompt; +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts index 953b49890a521..22679dd55ded0 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_container_id_from_signals.ts @@ -12,12 +12,12 @@ import { rangeQuery, typedSearch } from '@kbn/observability-plugin/server/utils/ import * as t from 'io-ts'; import moment from 'moment'; import { ESSearchRequest } from '@kbn/es-types'; +import { observabilityAlertDetailsContextRt } from '@kbn/observability-plugin/server/services'; import { ApmDocumentType } from '../../../../common/document_type'; import { APMEventClient, APMEventESSearchRequest, } from '../../../lib/helpers/create_es_client/create_apm_event_client'; -import { observabilityAlertDetailsContextRt } from '.'; import { RollupInterval } from '../../../../common/rollup'; export async function getContainerIdFromSignals({ @@ -28,7 +28,7 @@ export async function getContainerIdFromSignals({ }: { query: t.TypeOf; esClient: ElasticsearchClient; - coreContext: CoreRequestHandlerContext; + coreContext: Pick; apmEventClient: APMEventClient; }) { if (query['container.id']) { @@ -76,7 +76,7 @@ async function getContainerIdFromLogs({ }: { params: ESSearchRequest['body']; esClient: ElasticsearchClient; - coreContext: CoreRequestHandlerContext; + coreContext: Pick; }) { const index = await coreContext.uiSettings.client.get(aiAssistantLogsIndexPattern); const res = await typedSearch<{ container: { id: string } }, any>(esClient, { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts index 3f79178b76d19..bd62b998bee99 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/get_service_name_from_signals.ts @@ -12,12 +12,12 @@ import { rangeQuery, termQuery, typedSearch } from '@kbn/observability-plugin/se import * as t from 'io-ts'; import moment from 'moment'; import { ESSearchRequest } from '@kbn/es-types'; +import { observabilityAlertDetailsContextRt } from '@kbn/observability-plugin/server/services'; import { ApmDocumentType } from '../../../../common/document_type'; import { APMEventClient, APMEventESSearchRequest, } from '../../../lib/helpers/create_es_client/create_apm_event_client'; -import { observabilityAlertDetailsContextRt } from '.'; import { RollupInterval } from '../../../../common/rollup'; export async function getServiceNameFromSignals({ @@ -28,7 +28,7 @@ export async function getServiceNameFromSignals({ }: { query: t.TypeOf; esClient: ElasticsearchClient; - coreContext: CoreRequestHandlerContext; + coreContext: Pick; apmEventClient: APMEventClient; }) { if (query['service.name']) { @@ -85,7 +85,7 @@ async function getServiceNameFromLogs({ }: { params: ESSearchRequest['body']; esClient: ElasticsearchClient; - coreContext: CoreRequestHandlerContext; + coreContext: Pick; }) { const index = await coreContext.uiSettings.client.get(aiAssistantLogsIndexPattern); const res = await typedSearch<{ service: { name: string } }, any>(esClient, { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts index 0b76089d1e1c5..d6022876c9f3b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts @@ -8,37 +8,22 @@ import type { ScopedAnnotationsClient } from '@kbn/observability-plugin/server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { CoreRequestHandlerContext, Logger } from '@kbn/core/server'; +import { + AlertDetailsContextualInsight, + AlertDetailsContextualInsightsHandlerQuery, +} from '@kbn/observability-plugin/server/services'; import moment from 'moment'; -import * as t from 'io-ts'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import type { MlClient } from '../../../lib/helpers/get_ml_client'; import type { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import type { ApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client'; import { getApmServiceSummary } from '../get_apm_service_summary'; import { getAssistantDownstreamDependencies } from '../get_apm_downstream_dependencies'; import { getLogCategories } from '../get_log_categories'; -import { ApmTimeseriesType, getApmTimeseries } from '../get_apm_timeseries'; import { getAnomalies } from '../get_apm_service_summary/get_anomalies'; import { getServiceNameFromSignals } from './get_service_name_from_signals'; import { getContainerIdFromSignals } from './get_container_id_from_signals'; - -export const observabilityAlertDetailsContextRt = t.intersection([ - t.type({ - alert_started_at: t.string, - }), - t.partial({ - // apm fields - 'service.name': t.string, - 'service.environment': t.string, - 'transaction.type': t.string, - 'transaction.name': t.string, - - // infrastructure fields - 'host.name': t.string, - 'container.id': t.string, - 'kubernetes.pod.name': t.string, - }), -]); +import { getApmAlertDetailsContextPrompt } from './get_apm_alert_details_context_prompt'; +import { getExitSpanChangePoints, getServiceChangePoints } from '../get_changepoints'; export async function getObservabilityAlertDetailsContext({ coreContext, @@ -50,15 +35,15 @@ export async function getObservabilityAlertDetailsContext({ mlClient, query, }: { - coreContext: CoreRequestHandlerContext; + coreContext: Pick; annotationsClient?: ScopedAnnotationsClient; apmAlertsClient: ApmAlertsClient; apmEventClient: APMEventClient; esClient: ElasticsearchClient; logger: Logger; mlClient?: MlClient; - query: t.TypeOf; -}) { + query: AlertDetailsContextualInsightsHandlerQuery; +}): Promise { const alertStartedAt = query.alert_started_at; const serviceEnvironment = query['service.environment']; const hostName = query['host.name']; @@ -182,141 +167,14 @@ export async function getObservabilityAlertDetailsContext({ anomaliesPromise, ]); - return { + return getApmAlertDetailsContextPrompt({ + serviceName, + serviceEnvironment, serviceSummary, downstreamDependencies, logCategories, serviceChangePoints, exitSpanChangePoints, anomalies, - }; -} - -async function getServiceChangePoints({ - apmEventClient, - alertStartedAt, - serviceName, - serviceEnvironment, - transactionType, - transactionName, -}: { - apmEventClient: APMEventClient; - alertStartedAt: string; - serviceName: string | undefined; - serviceEnvironment: string | undefined; - transactionType: string | undefined; - transactionName: string | undefined; -}) { - if (!serviceName) { - return []; - } - - const res = await getApmTimeseries({ - apmEventClient, - arguments: { - start: moment(alertStartedAt).subtract(12, 'hours').toISOString(), - end: alertStartedAt, - stats: [ - { - title: 'Latency', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.transactionLatency, - function: LatencyAggregationType.p95, - 'transaction.type': transactionType, - 'transaction.name': transactionName, - }, - }, - { - title: 'Throughput', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.transactionThroughput, - 'transaction.type': transactionType, - 'transaction.name': transactionName, - }, - }, - { - title: 'Failure rate', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.transactionFailureRate, - 'transaction.type': transactionType, - 'transaction.name': transactionName, - }, - }, - { - title: 'Error events', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.errorEventRate, - }, - }, - ], - }, }); - - return res - .filter((timeseries) => timeseries.changes.length > 0) - .map((timeseries) => ({ - title: timeseries.stat.title, - grouping: timeseries.id, - changes: timeseries.changes, - })); -} - -async function getExitSpanChangePoints({ - apmEventClient, - alertStartedAt, - serviceName, - serviceEnvironment, -}: { - apmEventClient: APMEventClient; - alertStartedAt: string; - serviceName: string | undefined; - serviceEnvironment: string | undefined; -}) { - if (!serviceName) { - return []; - } - - const res = await getApmTimeseries({ - apmEventClient, - arguments: { - start: moment(alertStartedAt).subtract(30, 'minute').toISOString(), - end: alertStartedAt, - stats: [ - { - title: 'Exit span latency', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.exitSpanLatency, - }, - }, - { - title: 'Exit span failure rate', - 'service.name': serviceName, - 'service.environment': serviceEnvironment, - timeseries: { - name: ApmTimeseriesType.exitSpanFailureRate, - }, - }, - ], - }, - }); - - return res - .filter((timeseries) => timeseries.changes.length > 0) - .map((timeseries) => { - return { - title: timeseries.stat.title, - grouping: timeseries.id, - changes: timeseries.changes, - }; - }); } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts index a94b13b79577c..af3dfac613bd5 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/route.ts @@ -6,33 +6,26 @@ */ import * as t from 'io-ts'; import { omit } from 'lodash'; +import { + AlertDetailsContextualInsight, + observabilityAlertDetailsContextRt, +} from '@kbn/observability-plugin/server/services'; import { getApmAlertsClient } from '../../lib/helpers/get_apm_alerts_client'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { getMlClient } from '../../lib/helpers/get_ml_client'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { - observabilityAlertDetailsContextRt, - getObservabilityAlertDetailsContext, -} from './get_observability_alert_details_context'; +import { getObservabilityAlertDetailsContext } from './get_observability_alert_details_context'; import { downstreamDependenciesRouteRt, getAssistantDownstreamDependencies, type APMDownstreamDependency, } from './get_apm_downstream_dependencies'; -import { type ServiceSummary } from './get_apm_service_summary'; -import { ApmAnomalies } from './get_apm_service_summary/get_anomalies'; -import { - getApmTimeseries, - getApmTimeseriesRt, - TimeseriesChangePoint, - type ApmTimeseries, -} from './get_apm_timeseries'; -import { LogCategories } from './get_log_categories'; +import { getApmTimeseries, getApmTimeseriesRt, type ApmTimeseries } from './get_apm_timeseries'; const getObservabilityAlertDetailsContextRoute = createApmServerRoute({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', options: { tags: ['access:apm'], }, @@ -40,22 +33,7 @@ const getObservabilityAlertDetailsContextRoute = createApmServerRoute({ params: t.type({ query: observabilityAlertDetailsContextRt, }), - handler: async ( - resources - ): Promise<{ - serviceSummary?: ServiceSummary; - downstreamDependencies?: APMDownstreamDependency[]; - logCategories?: LogCategories; - serviceChangePoints?: Array<{ - title: string; - changes: TimeseriesChangePoint[]; - }>; - exitSpanChangePoints?: Array<{ - title: string; - changes: TimeseriesChangePoint[]; - }>; - anomalies?: ApmAnomalies; - }> => { + handler: async (resources): Promise<{ context: AlertDetailsContextualInsight[] }> => { const { context, request, plugins, logger, params } = resources; const { query } = params; @@ -74,7 +52,7 @@ const getObservabilityAlertDetailsContextRoute = createApmServerRoute({ ]); const esClient = coreContext.elasticsearch.client.asCurrentUser; - return getObservabilityAlertDetailsContext({ + const obsAlertContext = await getObservabilityAlertDetailsContext({ coreContext, annotationsClient, apmAlertsClient, @@ -84,6 +62,8 @@ const getObservabilityAlertDetailsContextRoute = createApmServerRoute({ mlClient, query, }); + + return { context: obsAlertContext }; }, }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts index 800dbb4455103..08bc47c44822a 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/agent_configuration/route.ts @@ -265,7 +265,7 @@ const agentConfigurationSearchRoute = createApmServerRoute({ ); if (willMarkAsApplied) { - markAppliedByAgent({ + await markAppliedByAgent({ id: configuration._id, body: configuration._source, internalESClient, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/create_or_update_custom_link.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/create_or_update_custom_link.test.ts index 97f75f2092710..3b5bf5b3f15a6 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/create_or_update_custom_link.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/settings/custom_link/create_or_update_custom_link.test.ts @@ -33,8 +33,8 @@ describe('Create or Update Custom link', () => { mockNow(1570737000000); }); - it('creates a new custom link', () => { - createOrUpdateCustomLink({ + it('creates a new custom link', async () => { + await createOrUpdateCustomLink({ customLink, internalESClient: mockInternalESClient, }); @@ -50,8 +50,8 @@ describe('Create or Update Custom link', () => { }, }); }); - it('update a new custom link', () => { - createOrUpdateCustomLink({ + it('update a new custom link', async () => { + await createOrUpdateCustomLink({ customLinkId: 'bar', customLink, internalESClient: mockInternalESClient, diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware.test.ts b/x-pack/plugins/observability_solution/apm_data_access/server/saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware.test.ts index 8b87b52dee166..9b6e34dac783a 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware.test.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware.test.ts @@ -35,8 +35,8 @@ describe('migrateLegacyAPMIndicesToSpaceAware', () => { }, } as unknown as CoreStart; - it('does not save any new saved object', () => { - migrateLegacyAPMIndicesToSpaceAware({ + it('does not save any new saved object', async () => { + await migrateLegacyAPMIndicesToSpaceAware({ coreStart: core, logger: loggerMock, }); diff --git a/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts b/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts index 6ea93a4302f49..73b2cbe87c138 100644 --- a/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts +++ b/x-pack/plugins/observability_solution/asset_manager/server/plugin.ts @@ -80,7 +80,7 @@ export class AssetManagerServerPlugin esClient: core.elasticsearch.client.asInternalUser, template: assetsIndexTemplateConfig, logger: this.logger, - }); + }).catch(() => {}); // it shouldn't reject, but just in case return {}; } diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index d1fbef7a7095e..b3ba3c5b184a9 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -82,7 +82,7 @@ const groupActionVariableDescription = i18n.translate( } ); -export async function registerInventoryThresholdRuleType( +export function registerInventoryThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs, { featureFlags }: InfraConfig diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts index f16e7dfd284d2..fa90cc6bf50fd 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts @@ -102,7 +102,7 @@ const viewInAppUrlActionVariableDescription = i18n.translate( } ); -export async function registerLogThresholdRuleType( +export function registerLogThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs, { featureFlags }: InfraConfig diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index e7ea693a0e74d..1adcd58f42592 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -55,7 +55,7 @@ export type MetricThresholdAlertType = Omit & { ActionGroupIdsOf: MetricThresholdAllowedActionGroups; }; -export async function registerMetricThresholdRuleType( +export function registerMetricThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs, { featureFlags }: InfraConfig diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx index b34cae81f303a..e26474b6165e6 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { ENABLE_ESQL } from '@kbn/esql-utils'; import { DataSourceSelection, isDatasetSelection } from '../../common/data_source_selection'; import { useKibanaContextForPlugin } from '../utils/use_kibana'; @@ -27,7 +27,7 @@ export const useEsql = ({ dataSourceSelection }: EsqlContextDeps): UseEsqlResult services: { uiSettings, discover }, } = useKibanaContextForPlugin(); - const isEsqlEnabled = uiSettings?.get('discover:enableESQL'); + const isEsqlEnabled = uiSettings?.get(ENABLE_ESQL); const esqlPattern = isDatasetSelection(dataSourceSelection) ? dataSourceSelection.selection.dataset.name diff --git a/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json index 7c4224eadac19..35bd2b0a7ffb2 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json @@ -46,6 +46,7 @@ "@kbn/unified-field-list", "@kbn/unified-search-plugin", "@kbn/xstate-utils", + "@kbn/esql-utils", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_service.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_service.ts index b9270f5ab38ae..61810c47ff8d2 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_service.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_entries/log_entries_service.ts @@ -21,7 +21,7 @@ export class LogEntriesService { core: CoreSetup, setupDeps: LogEntriesServiceSetupDeps ) { - core.getStartServices().then(([, startDeps, selfStartDeps]) => { + void core.getStartServices().then(([, startDeps, selfStartDeps]) => { setupDeps.data.search.registerSearchStrategy( LOG_ENTRIES_SEARCH_STRATEGY, logEntriesSearchStrategyProvider({ ...setupDeps, ...startDeps, ...selfStartDeps }) diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx index 5b043e3ac8928..1de4b4a136919 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details_contextual_insights.tsx @@ -10,7 +10,6 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import dedent from 'dedent'; -import { isEmpty } from 'lodash'; import { useKibana } from '../../utils/kibana_react'; import { AlertData } from '../../hooks/use_fetch_alert_detail'; @@ -29,7 +28,9 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu } try { - const res = await http.get('/internal/apm/assistant/get_obs_alert_details_context', { + const { context } = await http.get<{ + context: Array<{ description: string; data: unknown }>; + }>('/internal/apm/assistant/alert_details_contextual_insights', { query: { alert_started_at: new Date(alert.formatted.start).toISOString(), @@ -46,60 +47,9 @@ export function AlertDetailContextualInsights({ alert }: { alert: AlertData | nu }, }); - const { - serviceSummary, - downstreamDependencies, - logCategories, - serviceChangePoints, - exitSpanChangePoints, - anomalies, - } = res as any; - - const serviceName = fields['service.name']; - const serviceEnvironment = fields['service.environment']; - - const obsAlertContext = `${ - !isEmpty(serviceSummary) - ? `Metadata for the service where the alert occurred: -${JSON.stringify(serviceSummary, null, 2)}` - : '' - } - - ${ - !isEmpty(downstreamDependencies) - ? `Downstream dependencies from the service "${serviceName}". Problems in these services can negatively affect the performance of "${serviceName}": -${JSON.stringify(downstreamDependencies, null, 2)}` - : '' - } - - ${ - !isEmpty(serviceChangePoints) - ? `Significant change points for "${serviceName}". Use this to spot dips and spikes in throughput, latency and failure rate: - ${JSON.stringify(serviceChangePoints, null, 2)}` - : '' - } - - ${ - !isEmpty(exitSpanChangePoints) - ? `Significant change points for the dependencies of "${serviceName}". Use this to spot dips or spikes in throughput, latency and failure rate for downstream dependencies: - ${JSON.stringify(exitSpanChangePoints, null, 2)}` - : '' - } - - ${ - !isEmpty(logCategories) - ? `Log events occurring around the time of the alert: - ${JSON.stringify(logCategories, null, 2)}` - : '' - } - - ${ - !isEmpty(anomalies) - ? `Anomalies for services running in the environment "${serviceEnvironment}": - ${anomalies}` - : '' - } - `; + const obsAlertContext = context + .map(({ description, data }) => `${description}:\n${JSON.stringify(data, null, 2)}`) + .join('\n\n'); return observabilityAIAssistant.getContextualInsightMessages({ message: `I'm looking at an alert and trying to understand why it was triggered`, diff --git a/x-pack/plugins/observability_solution/observability/server/plugin.ts b/x-pack/plugins/observability_solution/observability/server/plugin.ts index 7b2f36d27fe4a..fad3db3f5fd10 100644 --- a/x-pack/plugins/observability_solution/observability/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/server/plugin.ts @@ -52,6 +52,7 @@ import { registerRuleTypes } from './lib/rules/register_rule_types'; import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository'; import { registerRoutes } from './routes/register_routes'; import { threshold } from './saved_objects/threshold'; +import { AlertDetailsContextualInsightsService } from './services'; import { uiSettings } from './ui_settings'; export type ObservabilityPluginSetup = ReturnType; @@ -99,6 +100,8 @@ export class ObservabilityPlugin implements Plugin { const logsExplorerLocator = plugins.share.url.locators.get(LOGS_EXPLORER_LOCATOR_ID); + const alertDetailsContextualInsightsService = new AlertDetailsContextualInsightsService(); + plugins.features.registerKibanaFeature({ id: casesFeatureId, name: i18n.translate('xpack.observability.featureRegistry.linkObservabilityTitle', { @@ -282,7 +285,7 @@ export class ObservabilityPlugin implements Plugin { logsExplorerLocator, }); - core.getStartServices().then(([coreStart, pluginStart]) => { + void core.getStartServices().then(([coreStart, pluginStart]) => { registerRoutes({ core, config, @@ -293,6 +296,9 @@ export class ObservabilityPlugin implements Plugin { }, spaces: pluginStart.spaces, ruleDataService, + assistant: { + alertDetailsContextualInsightsService, + }, getRulesClientWithRequest: pluginStart.alerting.getRulesClientWithRequest, }, logger: this.logger, @@ -312,6 +318,7 @@ export class ObservabilityPlugin implements Plugin { const api = await annotationsApiPromise; return api?.getScopedAnnotationsClient(...args); }, + alertDetailsContextualInsightsService, alertsLocator, }; } diff --git a/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts index 92980f20c4646..373e91d89a1c3 100644 --- a/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts @@ -19,6 +19,7 @@ import axios from 'axios'; import * as t from 'io-ts'; import { ObservabilityConfig } from '..'; import { getHTTPResponseCode, ObservabilityError } from '../errors'; +import { AlertDetailsContextualInsightsService } from '../services'; import { ObservabilityRequestHandlerContext } from '../types'; import { AbstractObservabilityServerRouteRepository } from './types'; @@ -36,6 +37,9 @@ export interface RegisterRoutesDependencies { }; spaces?: SpacesPluginStart; ruleDataService: RuleDataPluginService; + assistant: { + alertDetailsContextualInsightsService: AlertDetailsContextualInsightsService; + }; getRulesClientWithRequest: (request: KibanaRequest) => RulesClientApi; } diff --git a/x-pack/plugins/observability_solution/observability/server/services/index.test.ts b/x-pack/plugins/observability_solution/observability/server/services/index.test.ts new file mode 100644 index 0000000000000..d0dcb08e9f31d --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/server/services/index.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AlertDetailsContextualInsightsHandlerQuery, + AlertDetailsContextualInsightsRequestContext, + AlertDetailsContextualInsightsService, +} from '.'; + +describe('AlertDetailsContextualInsightsService', () => { + it('concatenates context from registered handlers', async () => { + const service = new AlertDetailsContextualInsightsService(); + service.registerHandler(async () => [{ key: 'foo', description: 'foo', data: 'hello' }]); + service.registerHandler(async () => [{ key: 'bar', description: 'bar', data: 'hello' }]); + const context = await service.getAlertDetailsContext( + {} as AlertDetailsContextualInsightsRequestContext, + {} as AlertDetailsContextualInsightsHandlerQuery + ); + + expect(context).toEqual([ + { key: 'foo', description: 'foo', data: 'hello' }, + { key: 'bar', description: 'bar', data: 'hello' }, + ]); + }); +}); diff --git a/x-pack/plugins/observability_solution/observability/server/services/index.ts b/x-pack/plugins/observability_solution/observability/server/services/index.ts new file mode 100644 index 0000000000000..7c20d191440d6 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/server/services/index.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { + IScopedClusterClient, + IUiSettingsClient, + KibanaRequest, + SavedObjectsClientContract, +} from '@kbn/core/server'; +import { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server'; +import { concat } from 'lodash'; + +export const observabilityAlertDetailsContextRt = t.intersection([ + t.type({ + alert_started_at: t.string, + }), + t.partial({ + // apm fields + 'service.name': t.string, + 'service.environment': t.string, + 'transaction.type': t.string, + 'transaction.name': t.string, + + // infrastructure fields + 'host.name': t.string, + 'container.id': t.string, + 'kubernetes.pod.name': t.string, + }), +]); + +export type AlertDetailsContextualInsightsHandlerQuery = t.TypeOf< + typeof observabilityAlertDetailsContextRt +>; + +export interface AlertDetailsContextualInsight { + key: string; + description: string; + data: unknown; +} + +export interface AlertDetailsContextualInsightsRequestContext { + request: KibanaRequest; + core: Promise<{ + elasticsearch: { + client: IScopedClusterClient; + }; + uiSettings: { + client: IUiSettingsClient; + globalClient: IUiSettingsClient; + }; + savedObjects: { + client: SavedObjectsClientContract; + }; + }>; + licensing: Promise; +} +type AlertDetailsContextualInsightsHandler = ( + context: AlertDetailsContextualInsightsRequestContext, + query: AlertDetailsContextualInsightsHandlerQuery +) => Promise; + +export class AlertDetailsContextualInsightsService { + private handlers: AlertDetailsContextualInsightsHandler[] = []; + + constructor() {} + + registerHandler(handler: AlertDetailsContextualInsightsHandler) { + this.handlers.push(handler); + } + + async getAlertDetailsContext( + context: AlertDetailsContextualInsightsRequestContext, + query: AlertDetailsContextualInsightsHandlerQuery + ): Promise { + if (this.handlers.length === 0) return []; + + return Promise.all(this.handlers.map((handler) => handler(context, query))).then((results) => { + const [head, ...rest] = results; + return concat(head, ...rest); + }); + } +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts index 66876bfe26085..dc8a9d46a7a06 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts @@ -43,6 +43,8 @@ export { aiAssistantSimulatedFunctionCalling, } from './ui_settings/settings_keys'; +export { concatenateChatCompletionChunks } from './utils/concatenate_chat_completion_chunks'; + export { DEFAULT_LANGUAGE_OPTION, LANGUAGE_OPTIONS } from './ui_settings/language_options'; export { isSupportedConnectorType } from './connectors'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/chat/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/chat/types.ts index 1440be7723b4b..2151f65b95f80 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/chat/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/chat/types.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; type ChatActionClickPayloadBase = { type: TType; @@ -14,7 +13,7 @@ type ChatActionClickPayloadExecuteEsql = ChatActionClickPayloadBase< | ChatActionClickType.executeEsqlQuery | ChatActionClickType.visualizeEsqlQuery | ChatActionClickType.updateVisualization, - { query: string; userOverrides?: TypedLensByValueInput } + { query: string; userOverrides?: unknown } >; export type ChatActionClickPayload = ChatActionClickPayloadExecuteEsql; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts index 69a1a6821197b..025edb80227a9 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts @@ -7,7 +7,6 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; -import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { Observable } from 'rxjs'; import type { MessageAddEvent, @@ -108,7 +107,6 @@ export interface ObservabilityAIAssistantPluginSetupDependencies { export interface ObservabilityAIAssistantPluginStartDependencies { licensing: LicensingPluginStart; security: SecurityPluginStart; - triggersActionsUi: TriggersAndActionsUIPublicPluginStart; } export interface ObservabilityAIAssistantPublicSetup {} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/index.ts index b67a9ab0a477c..344d46f557782 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/index.ts @@ -24,6 +24,8 @@ export { aiAssistantSimulatedFunctionCalling, } from '../common'; +export { streamIntoObservable } from './service/util/stream_into_observable'; + export const config: PluginConfigDescriptor = { deprecations: ({ unusedFromRoot }) => [ unusedFromRoot('xpack.observability.aiAssistant.enabled', { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts index c63a4a902b337..c0b37b7142a83 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts @@ -40,6 +40,7 @@ export type ObservabilityAIAssistantRequestHandlerContext = Omit< }; uiSettings: { client: IUiSettingsClient; + globalClient: IUiSettingsClient; }; savedObjects: { client: SavedObjectsClientContract; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/bedrock/process_bedrock_stream.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/bedrock/process_bedrock_stream.ts index 969bc5add78b1..0f520102aac2d 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/bedrock/process_bedrock_stream.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/bedrock/process_bedrock_stream.ts @@ -74,7 +74,7 @@ export function processBedrockStream() { subscriber.error(err); }, complete: () => { - nextPromise.then(() => subscriber.complete()); + nextPromise.then(() => subscriber.complete()).catch(() => {}); }, }); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts index e22f63cf92eb2..7bd31f0ef7689 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts @@ -236,7 +236,8 @@ describe('Observability AI Assistant client', () => { status: 'ok', data: titleLlmSimulator.stream, }); - }); + }) + .catch(reject); }; titleLlmPromiseReject = (error: Error) => { reject(error); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json index b3fe0f8c8f6fc..0db746a4ab80c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json @@ -13,41 +13,32 @@ "server/**/*" ], "kbn_references": [ + "@kbn/i18n", + "@kbn/core-analytics-browser", + "@kbn/analytics-client", + "@kbn/logging", "@kbn/core", - "@kbn/actions-plugin", - "@kbn/utility-types", "@kbn/server-route-repository", - "@kbn/logging", - "@kbn/config-schema", - "@kbn/security-plugin", - "@kbn/i18n", - "@kbn/spaces-plugin", + "@kbn/licensing-plugin", + "@kbn/actions-plugin", + "@kbn/std", + "@kbn/kibana-utils-plugin", "@kbn/kibana-react-plugin", "@kbn/shared-ux-utility", - "@kbn/alerting-plugin", - "@kbn/kibana-utils-plugin", + "@kbn/security-plugin", + "@kbn/utility-types-jest", + "@kbn/config-schema", "@kbn/io-ts-utils", - "@kbn/std", - "@kbn/alerting-plugin", - "@kbn/features-plugin", - "@kbn/lens-plugin", + "@kbn/utility-types", "@kbn/data-views-plugin", - "@kbn/task-manager-plugin", "@kbn/rule-registry-plugin", - "@kbn/licensing-plugin", - "@kbn/utility-types-jest", - "@kbn/analytics-client", - "@kbn/tooling-log", - "@kbn/babel-register", - "@kbn/dev-cli-runner", - "@kbn/core-analytics-browser", - "@kbn/expect", - "@kbn/apm-synthtrace-client", - "@kbn/apm-synthtrace", + "@kbn/alerting-plugin", + "@kbn/spaces-plugin", + "@kbn/task-manager-plugin", + "@kbn/apm-utils", + "@kbn/features-plugin", "@kbn/cloud-plugin", "@kbn/serverless", - "@kbn/triggers-actions-ui-plugin", - "@kbn/apm-utils" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/kibana.jsonc b/x-pack/plugins/observability_solution/observability_ai_assistant_app/kibana.jsonc index b64d31e3f13b9..17a9812631e39 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/kibana.jsonc @@ -11,6 +11,7 @@ "aiAssistantManagementSelection", "observabilityAIAssistant", "observabilityShared", + "observability", "actions", "data", "dataViews", diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/.eslintrc.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/.eslintrc.json similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/.eslintrc.json rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/.eslintrc.json diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/README.md b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/README.md similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/README.md rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/README.md diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/alert_templates/templates.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/alert_templates/templates.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/alert_templates/templates.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/alert_templates/templates.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/cli.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/cli.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/cli.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/cli.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/evaluation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts similarity index 99% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/evaluation.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts index 650a5cedceaa8..738285ffefc6e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/evaluation.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/evaluation.ts @@ -19,7 +19,7 @@ import * as table from 'table'; import { TableUserConfig } from 'table'; import { format, parse } from 'url'; import { ToolingLog } from '@kbn/tooling-log'; -import { MessageRole } from '../../common'; +import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { EvaluateWith, options } from './cli'; import { getServiceUrls } from './get_service_urls'; import { KibanaClient } from './kibana_client'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/get_service_urls.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/get_service_urls.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/get_service_urls.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/get_service_urls.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/index.js b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/index.js similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/index.js rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/index.js diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/kibana_client.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts similarity index 95% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/kibana_client.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts index 7604b7bca4a17..364ef1c44a48f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/kibana_client.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/kibana_client.ts @@ -5,6 +5,29 @@ * 2.0. */ +import { + BufferFlushEvent, + ChatCompletionChunkEvent, + ChatCompletionErrorCode, + ChatCompletionErrorEvent, + concatenateChatCompletionChunks, + ConversationCreateEvent, + FunctionDefinition, + isChatCompletionError, + MessageAddEvent, + StreamingChatResponseEvent, + StreamingChatResponseEventType, +} from '@kbn/observability-ai-assistant-plugin/common'; +import type { ObservabilityAIAssistantScreenContext } from '@kbn/observability-ai-assistant-plugin/common/types'; +import { throwSerializedChatCompletionErrors } from '@kbn/observability-ai-assistant-plugin/common/utils/throw_serialized_chat_completion_errors'; +import { + APIReturnType, + isSupportedConnectorType, + Message, + MessageRole, + ObservabilityAIAssistantAPIClientRequestParamsOf, +} from '@kbn/observability-ai-assistant-plugin/public'; +import { streamIntoObservable } from '@kbn/observability-ai-assistant-plugin/server'; import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosInstance, AxiosResponse, isAxiosError } from 'axios'; import { isArray, pick, remove } from 'lodash'; @@ -24,23 +47,6 @@ import { } from 'rxjs'; import { format, parse, UrlObject } from 'url'; import { inspect } from 'util'; -import { ChatCompletionErrorCode, isChatCompletionError, Message, MessageRole } from '../../common'; -import { isSupportedConnectorType } from '../../common/connectors'; -import { - BufferFlushEvent, - ChatCompletionChunkEvent, - ChatCompletionErrorEvent, - ConversationCreateEvent, - MessageAddEvent, - StreamingChatResponseEvent, - StreamingChatResponseEventType, -} from '../../common/conversation_complete'; -import { FunctionDefinition } from '../../common/functions/types'; -import { ObservabilityAIAssistantScreenContext } from '../../common/types'; -import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks'; -import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors'; -import { APIReturnType, ObservabilityAIAssistantAPIClientRequestParamsOf } from '../../public'; -import { streamIntoObservable } from '../../server/service/util/stream_into_observable'; import { EvaluationResult } from './types'; // eslint-disable-next-line spaced-comment diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/read_kibana_config.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/read_kibana_config.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/read_kibana_config.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/read_kibana_config.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/alerts/index.spec.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/alerts/index.spec.ts similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/alerts/index.spec.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/alerts/index.spec.ts index 304851e2b1e61..b746ea8d4645f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/alerts/index.spec.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/alerts/index.spec.ts @@ -11,12 +11,12 @@ import expect from '@kbn/expect'; import { RuleResponse } from '@kbn/alerting-plugin/common/routes/rule/response/types/v1'; import moment from 'moment'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { chatClient, kibanaClient, synthtraceEsClients, logger } from '../../services'; import { apmTransactionRateAIAssistant, customThresholdAIAssistantLogCount, } from '../../alert_templates/templates'; -import { MessageRole } from '../../../../common'; describe('alert function', () => { const ruleIds: any[] = []; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/apm/index.spec.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/apm/index.spec.ts similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/apm/index.spec.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/apm/index.spec.ts index 6d715dd911dbf..8668b3b984528 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/apm/index.spec.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/apm/index.spec.ts @@ -11,8 +11,8 @@ import expect from '@kbn/expect'; import moment from 'moment'; import { apm, timerange, serviceMap } from '@kbn/apm-synthtrace-client'; import { RuleResponse } from '@kbn/alerting-plugin/common/routes/rule/response/types/v1'; +import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { chatClient, kibanaClient, synthtraceEsClients } from '../../services'; -import { MessageRole } from '../../../../common'; import { apmErrorCountAIAssistant } from '../../alert_templates/templates'; describe('apm', () => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/elasticsearch/index.spec.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/elasticsearch/index.spec.ts similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/elasticsearch/index.spec.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/elasticsearch/index.spec.ts index 20f78d487a4bf..b79d55d81daf2 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/elasticsearch/index.spec.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/elasticsearch/index.spec.ts @@ -8,8 +8,8 @@ /// import expect from '@kbn/expect'; +import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { chatClient, esClient } from '../../services'; -import { MessageRole } from '../../../../common'; describe('elasticsearch functions', () => { describe('health', () => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/esql/index.spec.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/esql/index.spec.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/esql/index.spec.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/esql/index.spec.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/kb/index.spec.ts similarity index 95% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/kb/index.spec.ts index 4460fffda8355..d32dcae5ac8b5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/scenarios/kb/index.spec.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/scenarios/kb/index.spec.ts @@ -8,8 +8,8 @@ /// import expect from '@kbn/expect'; +import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; import { chatClient, esClient } from '../../services'; -import { MessageRole } from '../../../../common'; describe('kb functions', () => { it('summarizes and recalls information', async () => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/services/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/services/index.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/services/index.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/services/index.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/setup_synthtrace.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/setup_synthtrace.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/setup_synthtrace.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/setup_synthtrace.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/types.ts similarity index 92% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/types.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/types.ts index 20000b322d911..2e9545550840f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/evaluation/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/evaluation/types.ts @@ -6,7 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; -import { Message } from '../../common'; +import type { Message } from '@kbn/observability-ai-assistant-plugin/public'; import { KibanaClient } from './kibana_client'; import { SynthtraceEsClients } from './setup_synthtrace'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/extract_sections.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/extract_sections.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/extract_sections.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/extract_sections.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/format_esql_examples.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/format_esql_examples.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/format_esql_examples.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/format_esql_examples.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/index.js b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/index.js similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/index.js rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/index.js diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/load_esql_docs.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/load_esql_docs.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant/scripts/load_esql_docs/load_esql_docs.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant_app/scripts/load_esql_docs/load_esql_docs.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/esql_docs/esql-kibana.txt b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/esql_docs/esql-kibana.txt index 798a9befd7647..5fcce8c997ee7 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/esql_docs/esql-kibana.txt +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/esql_docs/esql-kibana.txt @@ -10,7 +10,7 @@ Get started with ES|QLedit To get started with ES|QL in Discover, open the main menu and select Discover. Next, from the Data views menu, select Try ES|QL. The ability to select ES|QL from the Data views menu can be enabled and -disabled using the `discover:enableESQL` setting from +disabled using the `enableESQL` setting from Advanced Settings. The query baredit After switching to ES|QL mode, the query bar shows a sample query. For example: diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts index 903c3c0c26826..63e06818a2b70 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts @@ -96,6 +96,7 @@ export class ObservabilityAIAssistantAppPlugin }, uiSettings: { client: coreStart.uiSettings.asScopedToClient(savedObjectsClient), + globalClient: coreStart.uiSettings.globalAsScopedToClient(savedObjectsClient), }, savedObjects: { client: savedObjectsClient, @@ -113,7 +114,12 @@ export class ObservabilityAIAssistantAppPlugin }; }; - plugins.actions.registerType(getObsAIAssistantConnectorType(initResources)); + plugins.actions.registerType( + getObsAIAssistantConnectorType( + initResources, + plugins.observability.alertDetailsContextualInsightsService + ) + ); plugins.alerting.registerConnectorAdapter(getObsAIAssistantConnectorAdapter()); return {}; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts index 190ce8c9ef95c..479ffeaa40f4f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -16,6 +16,7 @@ import { } from '.'; import { Observable } from 'rxjs'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; +import { AlertDetailsContextualInsightsService } from '@kbn/observability-plugin/server/services'; describe('observabilityAIAssistant rule_connector', () => { describe('getObsAIAssistantConnectorAdapter', () => { @@ -56,7 +57,10 @@ describe('observabilityAIAssistant rule_connector', () => { const initResources = jest .fn() .mockResolvedValue({} as ObservabilityAIAssistantRouteHandlerResources); - const connectorType = getObsAIAssistantConnectorType(initResources); + const connectorType = getObsAIAssistantConnectorType( + initResources, + new AlertDetailsContextualInsightsService() + ); expect(connectorType.id).toEqual(OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID); expect(connectorType.isSystemActionType).toEqual(true); expect(connectorType.minimumLicenseRequired).toEqual('enterprise'); @@ -66,7 +70,10 @@ describe('observabilityAIAssistant rule_connector', () => { const initResources = jest .fn() .mockResolvedValue({} as ObservabilityAIAssistantRouteHandlerResources); - const connectorType = getObsAIAssistantConnectorType(initResources); + const connectorType = getObsAIAssistantConnectorType( + initResources, + new AlertDetailsContextualInsightsService() + ); const result = await connectorType.executor({ actionId: 'observability-ai-assistant', request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), @@ -106,7 +113,10 @@ describe('observabilityAIAssistant rule_connector', () => { }, } as unknown as ObservabilityAIAssistantRouteHandlerResources); - const connectorType = getObsAIAssistantConnectorType(initResources); + const connectorType = getObsAIAssistantConnectorType( + initResources, + new AlertDetailsContextualInsightsService() + ); const result = await connectorType.executor({ actionId: 'observability-ai-assistant', request: getFakeKibanaRequest({ id: 'foo', api_key: 'bar' }), @@ -142,6 +152,26 @@ describe('observabilityAIAssistant rule_connector', () => { content: 'hello', }, }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.Assistant, + content: '', + function_call: { + name: 'get_alerts_context', + arguments: JSON.stringify({}), + trigger: MessageRole.Assistant as const, + }, + }, + }, + { + '@timestamp': expect.any(String), + message: { + role: MessageRole.User, + name: 'get_alerts_context', + content: expect.any(String), + }, + }, { '@timestamp': expect.any(String), message: { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts index b46fec93d1dd1..c6b8b56b45a87 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts @@ -6,6 +6,8 @@ */ import { filter } from 'rxjs'; +import { get } from 'lodash'; +import dedent from 'dedent'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { KibanaRequest, Logger } from '@kbn/core/server'; @@ -32,6 +34,7 @@ import { } from '@kbn/observability-ai-assistant-plugin/common'; import { concatenateChatCompletionChunks } from '@kbn/observability-ai-assistant-plugin/common/utils/concatenate_chat_completion_chunks'; import { CompatibleJSONSchema } from '@kbn/observability-ai-assistant-plugin/common/functions/types'; +import { AlertDetailsContextualInsightsService } from '@kbn/observability-plugin/server/services'; import { getSystemMessageFromInstructions } from '@kbn/observability-ai-assistant-plugin/server/service/util/get_system_message_from_instructions'; import { convertSchemaToOpenApi } from './convert_schema_to_open_api'; import { OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID } from '../../common/rule_connector'; @@ -87,7 +90,8 @@ export type ObsAIAssistantConnectorTypeExecutorOptions = ConnectorTypeExecutorOp >; export function getObsAIAssistantConnectorType( - initResources: (request: KibanaRequest) => Promise + initResources: (request: KibanaRequest) => Promise, + alertDetailsContextService: AlertDetailsContextualInsightsService ): ObsAIAssistantConnectorType { return { id: OBSERVABILITY_AI_ASSISTANT_CONNECTOR_ID, @@ -112,7 +116,7 @@ export function getObsAIAssistantConnectorType( }, renderParameterTemplates, executor(options) { - return executor(options, initResources); + return executor(options, initResources, alertDetailsContextService); }, }; } @@ -132,7 +136,8 @@ function renderParameterTemplates( async function executor( execOptions: ObsAIAssistantConnectorTypeExecutorOptions, - initResources: (request: KibanaRequest) => Promise + initResources: (request: KibanaRequest) => Promise, + alertDetailsContextService: AlertDetailsContextualInsightsService ): Promise> { const request = execOptions.request; const alerts = execOptions.params.alerts; @@ -172,9 +177,34 @@ async function executor( }); }); - const backgroundInstruction = getBackgroundProcessInstruction( + const backgroundInstruction = dedent( + `You are called as a background process because alerts have changed state. + As a background process you are not interacting with a user. Because of that DO NOT ask for user + input if tasked to execute actions. You can generate multiple responses in a row. + If available, include the link of the conversation at the end of your answer.` + ); + + const alertsContext = await getAlertsContext( execOptions.params.rule, - execOptions.params.alerts + execOptions.params.alerts, + async (alert: Record) => { + const prompt = await alertDetailsContextService.getAlertDetailsContext( + { + core: resources.context.core, + licensing: resources.context.licensing, + request: resources.request, + }, + { + alert_started_at: get(alert, 'kibana.alert.start'), + 'service.name': get(alert, 'service.name'), + 'service.environment': get(alert, 'service.environment'), + 'host.name': get(alert, 'host.name'), + } + ); + return prompt + .map(({ description, data }) => `${description}:\n${JSON.stringify(data, null, 2)}`) + .join('\n\n'); + } ); client @@ -206,6 +236,26 @@ async function executor( content: execOptions.params.message, }, }, + { + '@timestamp': new Date().toISOString(), + message: { + role: MessageRole.Assistant, + content: '', + function_call: { + name: 'get_alerts_context', + arguments: JSON.stringify({}), + trigger: MessageRole.Assistant as const, + }, + }, + }, + { + '@timestamp': new Date().toISOString(), + message: { + role: MessageRole.User, + name: 'get_alerts_context', + content: JSON.stringify({ context: alertsContext }), + }, + }, { '@timestamp': new Date().toISOString(), message: { @@ -268,23 +318,38 @@ export const getObsAIAssistantConnectorAdapter = (): ConnectorAdapter< }; }; -function getBackgroundProcessInstruction(rule: RuleType, alerts: AlertSummary) { - let instruction = `You are called as a background process because the following alerts have changed state for the rule ${JSON.stringify( - rule +async function getAlertsContext( + rule: RuleType, + alerts: AlertSummary, + getAlertContext: (alert: Record) => Promise +): Promise { + const getAlertGroupDetails = async (alertGroup: Array>) => { + const formattedDetails = await Promise.all( + alertGroup.map(async (alert) => { + return `- ${JSON.stringify( + alert + )}. The following contextual information is available:\n${await getAlertContext(alert)}`; + }) + ).then((messages) => messages.join('\n')); + + return formattedDetails; + }; + + let details = `The following alerts have changed state for the rule ${JSON.stringify( + rule, + null, + 2 )}:\n`; if (alerts.new.length > 0) { - instruction += `- ${alerts.new.length} alerts have fired: ${JSON.stringify(alerts.new)}\n`; + details += `- ${alerts.new.length} alerts have fired:\n${await getAlertGroupDetails( + alerts.new + )}\n`; } if (alerts.recovered.length > 0) { - instruction += `- ${alerts.recovered.length} alerts have recovered: ${JSON.stringify( + details += `- ${alerts.recovered.length} alerts have recovered\n: ${await getAlertGroupDetails( alerts.recovered )}\n`; } - instruction += - ' As a background process you are not interacting with a user. Because of that DO NOT ask for user'; - instruction += - ' input if tasked to execute actions. You can generate multiple responses in a row.'; - instruction += ' If available, include the link of the conversation at the end of your answer.'; - return instruction; + return details; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/types.ts index 902774bacd800..680e5b2409b7c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/types.ts @@ -37,6 +37,7 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityAIAssistantAppServerStart {} @@ -67,6 +68,7 @@ export interface ObservabilityAIAssistantAppPluginSetupDependencies { features: FeaturesPluginSetup; taskManager: TaskManagerSetupContract; dataViews: DataViewsServerPluginSetup; + observability: ObservabilityPluginSetup; cloud?: CloudSetup; serverless?: ServerlessPluginSetup; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index 8c7224647ce24..90c4f4d415142 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -13,55 +13,62 @@ "server/**/*" ], "kbn_references": [ - "@kbn/core", - "@kbn/logging", - "@kbn/config-schema", - "@kbn/security-plugin", - "@kbn/i18n", - "@kbn/kibana-react-plugin", - "@kbn/observability-shared-plugin", - "@kbn/lens-embeddable-utils", - "@kbn/field-formats-plugin", - "@kbn/lens-plugin", - "@kbn/data-views-plugin", - "@kbn/es-query", - "@kbn/rule-registry-plugin", - "@kbn/licensing-plugin", - "@kbn/share-plugin", - "@kbn/ui-actions-plugin", - "@kbn/expressions-plugin", - "@kbn/visualization-utils", - "@kbn/field-types", "@kbn/es-types", - "@kbn/esql-utils", "@kbn/observability-ai-assistant-plugin", + "@kbn/field-formats-plugin", + "@kbn/core", "@kbn/typed-react-router-config", + "@kbn/i18n", + "@kbn/management-settings-ids", + "@kbn/security-plugin", "@kbn/ui-theme", "@kbn/actions-plugin", "@kbn/user-profile-components", - "@kbn/i18n-react", - "@kbn/triggers-actions-ui-plugin", "@kbn/core-http-browser", + "@kbn/triggers-actions-ui-plugin", + "@kbn/shared-ux-utility", + "@kbn/i18n-react", "@kbn/code-editor", "@kbn/monaco", + "@kbn/data-views-plugin", + "@kbn/lens-embeddable-utils", + "@kbn/lens-plugin", + "@kbn/expressions-plugin", + "@kbn/ui-actions-plugin", + "@kbn/esql-utils", + "@kbn/visualization-utils", + "@kbn/ai-assistant-management-plugin", "@kbn/utility-types-jest", + "@kbn/kibana-react-plugin", + "@kbn/licensing-plugin", + "@kbn/logging", + "@kbn/deeplinks-observability", + "@kbn/share-plugin", + "@kbn/observability-shared-plugin", "@kbn/ml-plugin", + "@kbn/data-plugin", "@kbn/react-kibana-context-theme", "@kbn/shared-ux-link-redirect-app", - "@kbn/shared-ux-utility", - "@kbn/data-plugin", + "@kbn/dev-cli-runner", + "@kbn/tooling-log", + "@kbn/babel-register", + "@kbn/expect", + "@kbn/apm-synthtrace-client", + "@kbn/alerting-plugin", + "@kbn/apm-synthtrace", + "@kbn/apm-utils", + "@kbn/config-schema", + "@kbn/es-query", + "@kbn/rule-registry-plugin", "@kbn/esql-validation-autocomplete", "@kbn/esql-ast", - "@kbn/ai-assistant-management-plugin", - "@kbn/deeplinks-observability", - "@kbn/management-settings-ids", - "@kbn/apm-utils", - "@kbn/alerting-plugin", + "@kbn/field-types", "@kbn/stack-connectors-plugin", "@kbn/features-plugin", "@kbn/serverless", "@kbn/task-manager-plugin", - "@kbn/cloud-plugin" + "@kbn/cloud-plugin", + "@kbn/observability-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/profiling/server/plugin.ts b/x-pack/plugins/observability_solution/profiling/server/plugin.ts index 5bb7cfe978cc0..27fbc4f958da1 100644 --- a/x-pack/plugins/observability_solution/profiling/server/plugin.ts +++ b/x-pack/plugins/observability_solution/profiling/server/plugin.ts @@ -46,41 +46,44 @@ export class ProfilingPlugin PROFILING_SERVER_FEATURE_ID ); - core.getStartServices().then(([coreStart, depsStart]) => { - const profilingSpecificEsClient = config.elasticsearch - ? coreStart.elasticsearch.createClient('profiling', { - hosts: [config.elasticsearch.hosts], - username: config.elasticsearch.username, - password: config.elasticsearch.password, - }) - : undefined; + core + .getStartServices() + .then(([coreStart, depsStart]) => { + const profilingSpecificEsClient = config.elasticsearch + ? coreStart.elasticsearch.createClient('profiling', { + hosts: [config.elasticsearch.hosts], + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }) + : undefined; - registerRoutes({ - router, - logger: this.logger!, - dependencies: { - start: depsStart, - setup: deps, - config, - stackVersion, - telemetryUsageCounter, - }, - services: { - createProfilingEsClient: ({ - request, - esClient: defaultEsClient, - useDefaultAuth = false, - }) => { - const esClient = - profilingSpecificEsClient && !useDefaultAuth - ? profilingSpecificEsClient.asScoped(request).asInternalUser - : defaultEsClient; + registerRoutes({ + router, + logger: this.logger!, + dependencies: { + start: depsStart, + setup: deps, + config, + stackVersion, + telemetryUsageCounter, + }, + services: { + createProfilingEsClient: ({ + request, + esClient: defaultEsClient, + useDefaultAuth = false, + }) => { + const esClient = + profilingSpecificEsClient && !useDefaultAuth + ? profilingSpecificEsClient.asScoped(request).asInternalUser + : defaultEsClient; - return createProfilingEsClient({ request, esClient }); + return createProfilingEsClient({ request, esClient }); + }, }, - }, - }); - }); + }); + }) + .catch(() => {}); return {}; } diff --git a/x-pack/plugins/observability_solution/slo/common/locators/paths.ts b/x-pack/plugins/observability_solution/slo/common/locators/paths.ts index 2f2a1b329543b..198b433e285a2 100644 --- a/x-pack/plugins/observability_solution/slo/common/locators/paths.ts +++ b/x-pack/plugins/observability_solution/slo/common/locators/paths.ts @@ -4,11 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + export const SLOS_BASE_PATH = '/app/slos'; export const SLO_PREFIX = '/slos'; export const SLOS_PATH = '/' as const; export const SLOS_WELCOME_PATH = '/welcome' as const; -export const SLO_DETAIL_PATH = '/:sloId' as const; +export const SLO_DETAIL_PATH = '/:sloId/:tabId?' as const; export const SLO_CREATE_PATH = '/create' as const; export const SLO_EDIT_PATH = '/edit/:sloId' as const; export const SLOS_OUTDATED_DEFINITIONS_PATH = '/outdated-definitions' as const; @@ -25,10 +26,13 @@ export const paths = { sloEdit: (sloId: string) => `${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}`, sloEditWithEncodedForm: (sloId: string, encodedParams: string) => `${SLOS_BASE_PATH}/edit/${encodeURIComponent(sloId)}?_a=${encodedParams}`, - sloDetails: (sloId: string, instanceId?: string, remoteName?: string) => { + sloDetails: (sloId: string, instanceId?: string, remoteName?: string, tabId?: string) => { const qs = new URLSearchParams(); - if (!!instanceId) qs.append('instanceId', instanceId); + if (!!instanceId && instanceId !== '*') qs.append('instanceId', instanceId); if (!!remoteName) qs.append('remoteName', remoteName); + if (tabId) { + return `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}/${tabId}?${qs.toString()}`; + } return `${SLOS_BASE_PATH}/${encodeURIComponent(sloId)}?${qs.toString()}`; }, }; diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rate_header.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rate_header.tsx new file mode 100644 index 0000000000000..5df9bcb2255b4 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rate_header.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { SloTabId } from '../../../pages/slo_details/components/slo_details'; +import { BurnRateOption } from './burn_rates'; +interface Props { + burnRateOption: BurnRateOption; + setBurnRateOption: (option: BurnRateOption) => void; + burnRateOptions: BurnRateOption[]; + selectedTabId: SloTabId; +} +export function BurnRateHeader({ + burnRateOption, + burnRateOptions, + setBurnRateOption, + selectedTabId, +}: Props) { + const onBurnRateOptionChange = (optionId: string) => { + const selected = burnRateOptions.find((opt) => opt.id === optionId) ?? burnRateOptions[0]; + setBurnRateOption(selected); + }; + return ( + + + +

+ {i18n.translate('xpack.slo.burnRate.title', { + defaultMessage: 'Burn rate', + })} +

+
+
+ {selectedTabId !== 'history' && ( + + ({ id: opt.id, label: opt.label }))} + idSelected={burnRateOption.id} + onChange={onBurnRateOptionChange} + buttonSize="compressed" + /> + + )} +
+ ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rates.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rates.tsx index b6c828f9a4df6..cb7555dfeaec3 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rates.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/burn_rates.tsx @@ -5,11 +5,14 @@ * 2.0. */ -import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import moment from 'moment'; import React, { useEffect, useState } from 'react'; +import { TimeBounds } from '../../../pages/slo_details/types'; +import { TimeRange } from '../error_rate_chart/use_lens_definition'; +import { SloTabId } from '../../../pages/slo_details/components/slo_details'; +import { BurnRateHeader } from './burn_rate_header'; import { useFetchSloBurnRates } from '../../../hooks/use_fetch_slo_burn_rates'; import { ErrorRateChart } from '../error_rate_chart'; import { BurnRate } from './burn_rate'; @@ -18,6 +21,9 @@ interface Props { slo: SLOWithSummaryResponse; isAutoRefreshing?: boolean; burnRateOptions: BurnRateOption[]; + selectedTabId: SloTabId; + range?: TimeRange; + onBrushed?: (timeBounds: TimeBounds) => void; } export interface BurnRateOption { @@ -32,7 +38,14 @@ function getWindowsFromOptions(opts: BurnRateOption[]): Array<{ name: string; du return opts.map((opt) => ({ name: opt.windowName, duration: `${opt.duration}h` })); } -export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) { +export function BurnRates({ + slo, + isAutoRefreshing, + burnRateOptions, + selectedTabId, + range, + onBrushed, +}: Props) { const [burnRateOption, setBurnRateOption] = useState(burnRateOptions[0]); const { isLoading, data } = useFetchSloBurnRates({ slo, @@ -46,12 +59,7 @@ export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) { } }, [burnRateOptions]); - const onBurnRateOptionChange = (optionId: string) => { - const selected = burnRateOptions.find((opt) => opt.id === optionId) ?? burnRateOptions[0]; - setBurnRateOption(selected); - }; - - const dataTimeRange = { + const dataTimeRange = range ?? { from: moment().subtract(burnRateOption.duration, 'hour').toDate(), to: new Date(), }; @@ -64,34 +72,26 @@ export function BurnRates({ slo, isAutoRefreshing, burnRateOptions }: Props) { return ( - - - -

- {i18n.translate('xpack.slo.burnRate.title', { - defaultMessage: 'Burn rate', - })} -

-
-
- - ({ id: opt.id, label: opt.label }))} - idSelected={burnRateOption.id} - onChange={onBurnRateOptionChange} - buttonSize="compressed" - /> - -
+ - - - + {selectedTabId !== 'history' && ( + + + + )} - +
diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/error_rate_chart.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/error_rate_chart.tsx index 9353d1d574718..e42f5a9115bb6 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/error_rate_chart.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/error_rate_chart.tsx @@ -9,6 +9,8 @@ import { ViewMode } from '@kbn/embeddable-plugin/public'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import moment from 'moment'; import React from 'react'; +import { SloTabId } from '../../../pages/slo_details/components/slo_details'; +import { TimeBounds } from '../../../pages/slo_details/types'; import { useKibana } from '../../../utils/kibana_react'; import { getDelayInSecondsFromSLO } from '../../../utils/slo/get_delay_in_seconds_from_slo'; import { AlertAnnotation, TimeRange, useLensDefinition } from './use_lens_definition'; @@ -20,6 +22,8 @@ interface Props { alertTimeRange?: TimeRange; showErrorRateAsLine?: boolean; annotations?: AlertAnnotation[]; + selectedTabId?: SloTabId; + onBrushed?: (timeBounds: TimeBounds) => void; } export function ErrorRateChart({ @@ -29,17 +33,20 @@ export function ErrorRateChart({ alertTimeRange, showErrorRateAsLine, annotations, + onBrushed, + selectedTabId, }: Props) { const { lens: { EmbeddableComponent }, } = useKibana().services; - const lensDef = useLensDefinition( + const lensDef = useLensDefinition({ slo, threshold, alertTimeRange, annotations, - showErrorRateAsLine - ); + showErrorRateAsLine, + selectedTabId, + }); const delayInSeconds = getDelayInSecondsFromSLO(slo); const from = moment(dataTimeRange.from).subtract(delayInSeconds, 'seconds').toISOString(); @@ -55,6 +62,14 @@ export function ErrorRateChart({ }} attributes={lensDef} viewMode={ViewMode.VIEW} + onBrushEnd={({ range }) => { + onBrushed?.({ + from: range[0], + to: range[1], + fromUtc: moment(range[0]).format(), + toUtc: moment(range[1]).format(), + }); + }} noPadding /> ); diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/use_lens_definition.ts b/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/use_lens_definition.ts index a132076f8c2ed..513b26c6c2d18 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/use_lens_definition.ts +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/error_rate_chart/use_lens_definition.ts @@ -12,6 +12,7 @@ import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; +import { SloTabId } from '../../../pages/slo_details/components/slo_details'; import { SLO_DESTINATION_INDEX_PATTERN } from '../../../../common/constants'; export interface TimeRange { @@ -24,13 +25,21 @@ export interface AlertAnnotation { total: number; } -export function useLensDefinition( - slo: SLOWithSummaryResponse, - threshold: number, - alertTimeRange?: TimeRange, - annotations?: AlertAnnotation[], - showErrorRateAsLine?: boolean -): TypedLensByValueInput['attributes'] { +export function useLensDefinition({ + slo, + threshold, + alertTimeRange, + annotations, + showErrorRateAsLine, + selectedTabId, +}: { + slo: SLOWithSummaryResponse; + threshold: number; + alertTimeRange?: TimeRange; + annotations?: AlertAnnotation[]; + showErrorRateAsLine?: boolean; + selectedTabId?: SloTabId; +}): TypedLensByValueInput['attributes'] { const { euiTheme } = useEuiTheme(); const interval = 'auto'; @@ -87,20 +96,24 @@ export function useLensDefinition( }, ], }, - { - layerId: '34298f84-681e-4fa3-8107-d6facb32ed92', - layerType: 'referenceLine', - accessors: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6'], - yConfig: [ - { - forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6', - axisMode: 'left', - textVisibility: true, - color: euiTheme.colors.danger, - iconPosition: 'right', - }, - ], - }, + ...(selectedTabId !== 'history' + ? [ + { + layerId: '34298f84-681e-4fa3-8107-d6facb32ed92', + layerType: 'referenceLine', + accessors: ['0a42b72b-cd5a-4d59-81ec-847d97c268e6'], + yConfig: [ + { + forAccessor: '0a42b72b-cd5a-4d59-81ec-847d97c268e6', + axisMode: 'left', + textVisibility: true, + color: euiTheme.colors.danger, + iconPosition: 'right', + }, + ], + }, + ] + : []), ...(!!alertTimeRange ? [ { diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/common/slo_overview_details.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/common/slo_overview_details.tsx index 14100474e00b3..5bfc07f3562bd 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/common/slo_overview_details.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/common/slo_overview_details.tsx @@ -79,9 +79,9 @@ export function SloOverviewDetails({ {tabs.map((tab, index) => ( {tab.label} diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_historical_summary.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_historical_summary.ts index 0627b1b50c923..95abc489d69de 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_historical_summary.ts @@ -23,11 +23,16 @@ export interface UseFetchHistoricalSummaryResponse { export interface Params { sloList: SLOWithSummaryResponse[]; shouldRefetch?: boolean; + range?: { + from: string; + to: string; + }; } export function useFetchHistoricalSummary({ sloList = [], shouldRefetch, + range, }: Params): UseFetchHistoricalSummaryResponse { const { http } = useKibana().services; @@ -40,6 +45,7 @@ export function useFetchHistoricalSummary({ revision: slo.revision, objective: slo.objective, budgetingMethod: slo.budgetingMethod, + range, })); const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart.tsx index 552198aec1fa1..5b4794a3e9bcb 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart.tsx @@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { TimeBounds } from '../types'; +import { SloTabId } from './slo_details'; import { useKibana } from '../../../utils/kibana_react'; import { toDuration, toMinutes } from '../../../utils/slo/duration'; import { ChartData } from '../../../typings/slo'; @@ -33,9 +35,11 @@ export interface Props { data: ChartData[]; isLoading: boolean; slo: SLOWithSummaryResponse; + selectedTabId?: SloTabId; + onBrushed?: (timeBounds: TimeBounds) => void; } -export function ErrorBudgetChart({ data, isLoading, slo }: Props) { +export function ErrorBudgetChart({ data, isLoading, slo, selectedTabId, onBrushed }: Props) { const { uiSettings } = useKibana().services; const percentFormat = uiSettings.get('format:percent:defaultPattern'); const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED'; @@ -53,23 +57,12 @@ export function ErrorBudgetChart({ data, isLoading, slo }: Props) { } return ( <> - - - - - {errorBudgetTimeRemainingFormatted ? ( + {selectedTabId !== 'history' && ( + - ) : null} - + {errorBudgetTimeRemainingFormatted ? ( + + + + ) : null} + + )} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx index 11034718c9100..b8ffb2f30a79e 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_chart_panel.tsx @@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React, { useState, useCallback } from 'react'; import { SaveModalDashboardProps } from '@kbn/presentation-util-plugin/public'; +import { TimeBounds } from '../types'; +import { SloTabId } from './slo_details'; import { useKibana } from '../../../utils/kibana_react'; import { ChartData } from '../../../typings/slo'; import { ErrorBudgetChart } from './error_budget_chart'; @@ -24,9 +26,11 @@ export interface Props { data: ChartData[]; isLoading: boolean; slo: SLOWithSummaryResponse; + selectedTabId: SloTabId; + onBrushed?: (timeBounds: TimeBounds) => void; } -export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) { +export function ErrorBudgetChartPanel({ data, isLoading, slo, selectedTabId, onBrushed }: Props) { const [isMouseOver, setIsMouseOver] = useState(false); const [isDashboardAttachmentReady, setDashboardAttachmentReady] = useState(false); @@ -81,9 +85,16 @@ export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) { showTitle={true} isMouseOver={isMouseOver} setDashboardAttachmentReady={setDashboardAttachmentReady} + selectedTabId={selectedTabId} /> - +
{isDashboardAttachmentReady ? ( diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_header.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_header.tsx index b4cf6ae23cbb1..4a484d9df013f 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_header.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/error_budget_header.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { SloTabId } from './slo_details'; import { useKibana } from '../../../utils/kibana_react'; import { toDurationAdverbLabel, toDurationLabel } from '../../../utils/slo/labels'; @@ -19,6 +20,7 @@ interface Props { showTitle?: boolean; isMouseOver?: boolean; setDashboardAttachmentReady?: (value: boolean) => void; + selectedTabId?: SloTabId; } export function ErrorBudgetHeader({ @@ -26,6 +28,7 @@ export function ErrorBudgetHeader({ showTitle = true, isMouseOver, setDashboardAttachmentReady, + selectedTabId, }: Props) { const { executionContext } = useKibana().services; const executionContextName = executionContext.get().name; @@ -57,16 +60,18 @@ export function ErrorBudgetHeader({ )} - - - {rollingTimeWindowTypeSchema.is(slo.timeWindow.type) - ? i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.duration', { - defaultMessage: 'Last {duration}', - values: { duration: toDurationLabel(slo.timeWindow.duration) }, - }) - : toDurationAdverbLabel(slo.timeWindow.duration)} - - + {selectedTabId !== 'history' && ( + + + {rollingTimeWindowTypeSchema.is(slo.timeWindow.type) + ? i18n.translate('xpack.slo.sloDetails.errorBudgetChartPanel.duration', { + defaultMessage: 'Last {duration}', + values: { duration: toDurationLabel(slo.timeWindow.duration) }, + }) + : toDurationAdverbLabel(slo.timeWindow.duration)} + + + )} ); } diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx index e2f85a9d47e68..340d6ce465884 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/events_chart_panel.tsx @@ -36,6 +36,9 @@ import { max, min } from 'lodash'; import moment from 'moment'; import React, { useRef } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { TimeBounds } from '../types'; +import { getBrushData } from '../../../utils/slo/duration'; +import { SloTabId } from './slo_details'; import { useGetPreviewData } from '../../../hooks/use_get_preview_data'; import { useKibana } from '../../../utils/kibana_react'; import { COMPARATOR_MAPPING } from '../../slo_edit/constants'; @@ -48,9 +51,11 @@ export interface Props { start: number; end: number; }; + selectedTabId: SloTabId; + onBrushed?: (timeBounds: TimeBounds) => void; } -export function EventsChartPanel({ slo, range }: Props) { +export function EventsChartPanel({ slo, range, selectedTabId, onBrushed }: Props) { const { charts, uiSettings, discover } = useKibana().services; const { euiTheme } = useEuiTheme(); const baseTheme = charts.theme.useChartsBaseTheme(); @@ -157,13 +162,15 @@ export function EventsChartPanel({ slo, range }: Props) { {title} - - - {i18n.translate('xpack.slo.sloDetails.eventsChartPanel.duration', { - defaultMessage: 'Last 24h', - })} - - + {selectedTabId !== 'history' && ( + + + {i18n.translate('xpack.slo.sloDetails.eventsChartPanel.duration', { + defaultMessage: 'Last 24h', + })} + + + )} {showViewEventsLink && ( @@ -193,6 +200,7 @@ export function EventsChartPanel({ slo, range }: Props) { data={data || []} annotation={annotation} slo={slo} + onBrushed={onBrushed} /> ) : ( <> @@ -225,6 +233,9 @@ export function EventsChartPanel({ slo, range }: Props) { pointerUpdateDebounce={0} pointerUpdateTrigger={'x'} locale={i18n.getLocale()} + onBrushEnd={(brushArea) => { + onBrushed?.(getBrushData(brushArea)); + }} /> {annotation} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/historical_data_charts.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/historical_data_charts.tsx new file mode 100644 index 0000000000000..f2e096195f474 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/historical_data_charts.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { TimeBounds } from '../types'; +import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary'; +import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; +import { SloTabId } from './slo_details'; +import { SliChartPanel } from './sli_chart_panel'; +import { ErrorBudgetChartPanel } from './error_budget_chart_panel'; + +export interface Props { + slo: SLOWithSummaryResponse; + isAutoRefreshing: boolean; + selectedTabId: SloTabId; + range?: { + from: string; + to: string; + }; + onBrushed?: (timeBounds: TimeBounds) => void; +} +export function HistoricalDataCharts({ + slo, + range, + isAutoRefreshing, + selectedTabId, + onBrushed, +}: Props) { + const { data: historicalSummaries = [], isLoading: historicalSummaryLoading } = + useFetchHistoricalSummary({ + sloList: [slo], + shouldRefetch: isAutoRefreshing, + range, + }); + + const sloHistoricalSummary = historicalSummaries.find( + (historicalSummary) => + historicalSummary.sloId === slo.id && + historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE) + ); + + const errorBudgetBurnDownData = formatHistoricalData( + sloHistoricalSummary?.data, + 'error_budget_remaining' + ); + const historicalSliData = formatHistoricalData(sloHistoricalSummary?.data, 'sli_value'); + + return ( + <> + + + + + + + + ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/history/slo_details_history.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/history/slo_details_history.tsx new file mode 100644 index 0000000000000..0e20cb3960fbc --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/history/slo_details_history.tsx @@ -0,0 +1,128 @@ +/* + * 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, { useCallback, useMemo, useState } from 'react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSuperDatePicker, + OnTimeChangeProps, + OnRefreshProps, + EuiSpacer, +} from '@elastic/eui'; +import DateMath from '@kbn/datemath'; +import { useKibana } from '../../../../utils/kibana_react'; +import { HistoricalDataCharts } from '../historical_data_charts'; +import { useBurnRateOptions } from '../../hooks/use_burn_rate_options'; +import { SloTabId } from '../slo_details'; +import { BurnRates } from '../../../../components/slo/burn_rate/burn_rates'; +import { EventsChartPanel } from '../events_chart_panel'; +export interface Props { + slo: SLOWithSummaryResponse; + isAutoRefreshing: boolean; + selectedTabId: SloTabId; +} + +export function SLODetailsHistory({ slo, isAutoRefreshing, selectedTabId }: Props) { + const { uiSettings } = useKibana().services; + + const { burnRateOptions } = useBurnRateOptions(slo); + + const [start, setStart] = useState('now-30d'); + const [end, setEnd] = useState('now'); + + const onTimeChange = (val: OnTimeChangeProps) => { + setStart(val.start); + setEnd(val.end); + }; + + const onRefresh = (val: OnRefreshProps) => {}; + + const absRange = useMemo(() => { + return { + from: new Date(DateMath.parse(start)!.valueOf()), + to: new Date(DateMath.parse(end, { roundUp: true })!.valueOf()), + absoluteFrom: DateMath.parse(start)!.valueOf(), + absoluteTo: DateMath.parse(end, { roundUp: true })!.valueOf(), + }; + }, [start, end]); + + const onBrushed = useCallback(({ fromUtc, toUtc }) => { + setStart(fromUtc); + setEnd(toUtc); + }, []); + + return ( + <> + + + { + return { + start: from, + end: to, + label: display, + }; + })} + /> + + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/sli_chart_panel.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/sli_chart_panel.tsx index 788303b43ffa1..b4b90ae4efab1 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/sli_chart_panel.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/sli_chart_panel.tsx @@ -10,6 +10,8 @@ import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import { rollingTimeWindowTypeSchema, SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { TimeBounds } from '../types'; +import { SloTabId } from './slo_details'; import { useKibana } from '../../../utils/kibana_react'; import { ChartData } from '../../../typings/slo'; import { toDurationAdverbLabel, toDurationLabel } from '../../../utils/slo/labels'; @@ -19,9 +21,11 @@ export interface Props { data: ChartData[]; isLoading: boolean; slo: SLOWithSummaryResponse; + selectedTabId: SloTabId; + onBrushed?: (timeBounds: TimeBounds) => void; } -export function SliChartPanel({ data, isLoading, slo }: Props) { +export function SliChartPanel({ data, isLoading, slo, selectedTabId, onBrushed }: Props) { const { uiSettings } = useKibana().services; const percentFormat = uiSettings.get('format:percent:defaultPattern'); @@ -41,41 +45,45 @@ export function SliChartPanel({ data, isLoading, slo }: Props) { - - - {rollingTimeWindowTypeSchema.is(slo.timeWindow.type) - ? i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.duration', { - defaultMessage: 'Last {duration}', - values: { duration: toDurationLabel(slo.timeWindow.duration) }, - }) - : toDurationAdverbLabel(slo.timeWindow.duration)} - - + {selectedTabId !== 'history' && ( + + + {rollingTimeWindowTypeSchema.is(slo.timeWindow.type) + ? i18n.translate('xpack.slo.sloDetails.sliHistoryChartPanel.duration', { + defaultMessage: 'Last {duration}', + values: { duration: toDurationLabel(slo.timeWindow.duration) }, + }) + : toDurationAdverbLabel(slo.timeWindow.duration)} + + + )} - - - - - - - - + {selectedTabId !== 'history' && ( + + + + + + + + + )} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/slo_details.tsx index 01d62098e8802..cb5a1420dc06a 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/slo_details.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/slo_details.tsx @@ -5,71 +5,26 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React, { useEffect, useState } from 'react'; -import { BurnRateOption, BurnRates } from '../../../components/slo/burn_rate/burn_rates'; -import { useFetchHistoricalSummary } from '../../../hooks/use_fetch_historical_summary'; -import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo'; -import { formatHistoricalData } from '../../../utils/slo/chart_data_formatter'; -import { ErrorBudgetChartPanel } from './error_budget_chart_panel'; +import { HistoricalDataCharts } from './historical_data_charts'; +import { useBurnRateOptions } from '../hooks/use_burn_rate_options'; +import { SLODetailsHistory } from './history/slo_details_history'; +import { BurnRates } from '../../../components/slo/burn_rate/burn_rates'; import { EventsChartPanel } from './events_chart_panel'; import { Overview } from './overview/overview'; -import { SliChartPanel } from './sli_chart_panel'; import { SloDetailsAlerts } from './slo_detail_alerts'; import { SloHealthCallout } from './slo_health_callout'; import { SloRemoteCallout } from './slo_remote_callout'; export const TAB_ID_URL_PARAM = 'tabId'; export const OVERVIEW_TAB_ID = 'overview'; +export const HISTORY_TAB_ID = 'history'; export const ALERTS_TAB_ID = 'alerts'; const DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000; -const DEFAULT_BURN_RATE_OPTIONS: BurnRateOption[] = [ - { - id: htmlIdGenerator()(), - label: i18n.translate('xpack.slo.burnRates.fromRange.label', { - defaultMessage: '{duration}h', - values: { duration: 1 }, - }), - windowName: 'CRITICAL', - threshold: 14.4, - duration: 1, - }, - { - id: htmlIdGenerator()(), - label: i18n.translate('xpack.slo.burnRates.fromRange.label', { - defaultMessage: '{duration}h', - values: { duration: 6 }, - }), - windowName: 'HIGH', - threshold: 6, - duration: 6, - }, - { - id: htmlIdGenerator()(), - label: i18n.translate('xpack.slo.burnRates.fromRange.label', { - defaultMessage: '{duration}h', - values: { duration: 24 }, - }), - windowName: 'MEDIUM', - threshold: 3, - duration: 24, - }, - { - id: htmlIdGenerator()(), - label: i18n.translate('xpack.slo.burnRates.fromRange.label', { - defaultMessage: '{duration}h', - values: { duration: 72 }, - }), - windowName: 'LOW', - threshold: 1, - duration: 72, - }, -]; - -export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID; +export type SloTabId = typeof OVERVIEW_TAB_ID | typeof ALERTS_TAB_ID | typeof HISTORY_TAB_ID; export interface Props { slo: SLOWithSummaryResponse; @@ -77,30 +32,7 @@ export interface Props { selectedTabId: SloTabId; } export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) { - const { data: rules } = useFetchRulesForSlo({ sloIds: [slo.id] }); - const burnRateOptions = - rules?.[slo.id]?.[0]?.params?.windows?.map((window) => ({ - id: htmlIdGenerator()(), - label: i18n.translate('xpack.slo.burnRates.fromRange.label', { - defaultMessage: '{duration}h', - values: { duration: window.longWindow.value }, - }), - windowName: window.actionGroup, - threshold: window.burnRateThreshold, - duration: window.longWindow.value, - })) ?? DEFAULT_BURN_RATE_OPTIONS; - - const { data: historicalSummaries = [], isLoading: historicalSummaryLoading } = - useFetchHistoricalSummary({ - sloList: [slo], - shouldRefetch: isAutoRefreshing, - }); - - const sloHistoricalSummary = historicalSummaries.find( - (historicalSummary) => - historicalSummary.sloId === slo.id && - historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE) - ); + const { burnRateOptions } = useBurnRateOptions(slo); const [range, setRange] = useState({ start: new Date().getTime() - DAY_IN_MILLISECONDS, @@ -118,12 +50,6 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) { return () => clearInterval(intervalId); }, [isAutoRefreshing]); - const errorBudgetBurnDownData = formatHistoricalData( - sloHistoricalSummary?.data, - 'error_budget_remaining' - ); - const historicalSliData = formatHistoricalData(sloHistoricalSummary?.data, 'sli_value'); - return selectedTabId === OVERVIEW_TAB_ID ? ( @@ -137,24 +63,26 @@ export function SloDetails({ slo, isAutoRefreshing, selectedTabId }: Props) { slo={slo} isAutoRefreshing={isAutoRefreshing} burnRateOptions={burnRateOptions} + selectedTabId={selectedTabId} /> + - - - - - - - + - ) : ( + ) : selectedTabId === ALERTS_TAB_ID ? ( + ) : ( + ); } diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/wide_chart.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/wide_chart.tsx index d737590b190a5..5d6d68ae7d892 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/wide_chart.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/wide_chart.tsx @@ -24,6 +24,8 @@ import moment from 'moment'; import React, { useRef } from 'react'; import { i18n } from '@kbn/i18n'; +import { getBrushData } from '../../../utils/slo/duration'; +import { TimeBounds } from '../types'; import { useKibana } from '../../../utils/kibana_react'; import { ChartData } from '../../../typings'; @@ -36,9 +38,10 @@ export interface Props { chart: ChartType; state: State; isLoading: boolean; + onBrushed?: (timeBounds: TimeBounds) => void; } -export function WideChart({ chart, data, id, isLoading, state }: Props) { +export function WideChart({ chart, data, id, isLoading, state, onBrushed }: Props) { const { charts, uiSettings } = useKibana().services; const baseTheme = charts.theme.useChartsBaseTheme(); const { euiTheme } = useEuiTheme(); @@ -63,7 +66,16 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) { } + noResults={ + + } onPointerUpdate={handleCursorUpdate} externalPointerEvents={{ tooltip: { visible: true }, @@ -71,6 +83,9 @@ export function WideChart({ chart, data, id, isLoading, state }: Props) { pointerUpdateDebounce={0} pointerUpdateTrigger={'x'} locale={i18n.getLocale()} + onBrushEnd={(brushArea) => { + onBrushed?.(getBrushData(brushArea)); + }} /> { + const { data: rules } = useFetchRulesForSlo({ sloIds: [slo.id] }); + const burnRateOptions = + rules?.[slo.id]?.[0]?.params?.windows?.map((window) => ({ + id: htmlIdGenerator()(), + label: i18n.translate('xpack.slo.burnRates.fromRange.label', { + defaultMessage: '{duration}h', + values: { duration: window.longWindow.value }, + }), + windowName: window.actionGroup, + threshold: window.burnRateThreshold, + duration: window.longWindow.value, + })) ?? DEFAULT_BURN_RATE_OPTIONS; + + return { burnRateOptions }; +}; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_selected_tab.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_selected_tab.ts new file mode 100644 index 0000000000000..1b1ff616e9c09 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_selected_tab.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { SloDetailsPathParams } from '../types'; +import { + ALERTS_TAB_ID, + HISTORY_TAB_ID, + OVERVIEW_TAB_ID, + SloTabId, +} from '../components/slo_details'; + +export const useSelectedTab = () => { + const { tabId } = useParams(); + + const [selectedTabId, setSelectedTabId] = useState(() => { + return tabId && [OVERVIEW_TAB_ID, ALERTS_TAB_ID, HISTORY_TAB_ID].includes(tabId) + ? (tabId as SloTabId) + : OVERVIEW_TAB_ID; + }); + + useEffect(() => { + // update the url when the selected tab changes + if (tabId !== selectedTabId) { + setSelectedTabId(tabId as SloTabId); + } + }, [selectedTabId, tabId]); + + return { + selectedTabId: selectedTabId || OVERVIEW_TAB_ID, + setSelectedTabId, + }; +}; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_details_tabs.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_details_tabs.tsx index 5926d4f2cf406..8eb954d4b5771 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_details_tabs.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_slo_details_tabs.tsx @@ -9,8 +9,15 @@ import { i18n } from '@kbn/i18n'; import { EuiNotificationBadge, EuiToolTip } from '@elastic/eui'; import React from 'react'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { paths } from '../../../../common/locators/paths'; +import { useKibana } from '../../../utils/kibana_react'; import { useFetchActiveAlerts } from '../../../hooks/use_fetch_active_alerts'; -import { ALERTS_TAB_ID, OVERVIEW_TAB_ID, SloTabId } from '../components/slo_details'; +import { + ALERTS_TAB_ID, + HISTORY_TAB_ID, + OVERVIEW_TAB_ID, + SloTabId, +} from '../components/slo_details'; export const useSloDetailsTabs = ({ slo, @@ -21,13 +28,15 @@ export const useSloDetailsTabs = ({ slo?: SLOWithSummaryResponse | null; isAutoRefreshing: boolean; selectedTabId: SloTabId; - setSelectedTabId: (val: SloTabId) => void; + setSelectedTabId?: (val: SloTabId) => void; }) => { const { data: activeAlerts } = useFetchActiveAlerts({ sloIdsAndInstanceIds: slo ? [[slo.id, slo.instanceId ?? ALL_VALUE]] : [], shouldRefetch: isAutoRefreshing, }); + const { basePath } = useKibana().services.http; + const isRemote = !!slo?.remote; const tabs = [ @@ -38,8 +47,47 @@ export const useSloDetailsTabs = ({ }), 'data-test-subj': 'overviewTab', isSelected: selectedTabId === OVERVIEW_TAB_ID, - onClick: () => setSelectedTabId(OVERVIEW_TAB_ID), + ...(setSelectedTabId + ? { + onClick: () => setSelectedTabId(OVERVIEW_TAB_ID), + } + : { + href: slo + ? `${basePath.get()}${paths.sloDetails( + slo.id, + slo.instanceId, + slo.remote?.remoteName, + OVERVIEW_TAB_ID + )}` + : undefined, + }), }, + ...(slo?.timeWindow.type === 'rolling' + ? [ + { + id: HISTORY_TAB_ID, + label: i18n.translate('xpack.slo.sloDetails.tab.historyLabel', { + defaultMessage: 'History', + }), + 'data-test-subj': 'historyTab', + isSelected: selectedTabId === HISTORY_TAB_ID, + ...(setSelectedTabId + ? { + onClick: () => setSelectedTabId(HISTORY_TAB_ID), + } + : { + href: slo + ? `${basePath.get()}${paths.sloDetails( + slo.id, + slo.instanceId, + slo.remote?.remoteName, + HISTORY_TAB_ID + )}` + : undefined, + }), + }, + ] + : []), { id: ALERTS_TAB_ID, label: isRemote ? ( @@ -63,7 +111,20 @@ export const useSloDetailsTabs = ({ {(activeAlerts && activeAlerts.get(slo)) ?? 0} ) : null, - onClick: () => setSelectedTabId(ALERTS_TAB_ID), + ...(setSelectedTabId + ? { + onClick: () => setSelectedTabId(ALERTS_TAB_ID), + } + : { + href: slo + ? `${basePath.get()}${paths.sloDetails( + slo.id, + slo.instanceId, + slo.remote?.remoteName, + ALERTS_TAB_ID + )}` + : undefined, + }), }, ]; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.test.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.test.tsx index cb8c8b37868d8..dd0b22e177701 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.test.tsx @@ -79,6 +79,7 @@ const mockKibana = () => { http: { basePath: { prepend: (url: string) => url, + get: () => 'http://localhost:5601', }, }, dataViews: { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx index 1fddae776ec01..3665bd68629f4 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useState } from 'react'; -import { useLocation, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { useIsMutating } from '@tanstack/react-query'; import { EuiLoadingSpinner, EuiSkeletonText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -16,6 +16,7 @@ import type { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import dedent from 'dedent'; +import { useSelectedTab } from './hooks/use_selected_tab'; import { HeaderMenu } from '../../components/header_menu/header_menu'; import { useSloDetailsTabs } from './hooks/use_slo_details_tabs'; import { useKibana } from '../../utils/kibana_react'; @@ -23,13 +24,7 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details'; import { useLicense } from '../../hooks/use_license'; import PageNotFound from '../404'; -import { - ALERTS_TAB_ID, - OVERVIEW_TAB_ID, - SloDetails, - TAB_ID_URL_PARAM, - SloTabId, -} from './components/slo_details'; +import { SloDetails } from './components/slo_details'; import { HeaderTitle } from './components/header_title'; import { HeaderControl } from './components/header_control'; import { paths } from '../../../common/locators/paths'; @@ -45,7 +40,6 @@ export function SloDetailsPage() { observabilityAIAssistant, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); - const { search } = useLocation(); const { hasAtLeast } = useLicense(); const hasRightLicense = hasAtLeast('platinum'); @@ -61,19 +55,12 @@ export function SloDetailsPage() { }); const isDeleting = Boolean(useIsMutating(['deleteSlo'])); - const [selectedTabId, setSelectedTabId] = useState(() => { - const searchParams = new URLSearchParams(search); - const urlTabId = searchParams.get(TAB_ID_URL_PARAM); - return urlTabId && [OVERVIEW_TAB_ID, ALERTS_TAB_ID].includes(urlTabId) - ? (urlTabId as SloTabId) - : OVERVIEW_TAB_ID; - }); + const { selectedTabId } = useSelectedTab(); const { tabs } = useSloDetailsTabs({ slo, isAutoRefreshing, selectedTabId, - setSelectedTabId, }); useBreadcrumbs(getBreadcrumbs(basePath, slo)); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/types.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/types.ts index 46aaa1b02f38b..7117cdd39db2c 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/types.ts +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/types.ts @@ -7,4 +7,12 @@ export interface SloDetailsPathParams { sloId: string; + tabId?: string; +} + +export interface TimeBounds { + from: number; + to: number; + fromUtc: string; + toUtc: string; } diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx index f4d2b7aa2ceee..3f0e88b912ec2 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/good_bad_events_chart.tsx @@ -23,6 +23,8 @@ import { i18n } from '@kbn/i18n'; import { GetPreviewDataResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import moment from 'moment'; import React, { useRef } from 'react'; +import { TimeBounds } from '../../../slo_details/types'; +import { getBrushData } from '../../../../utils/slo/duration'; import { useKibana } from '../../../../utils/kibana_react'; import { openInDiscover } from '../../../../utils/slo/get_discover_link'; @@ -32,6 +34,7 @@ export interface Props { annotation?: React.ReactNode; isLoading?: boolean; bottomTitle?: string; + onBrushed?: (timeBounds: TimeBounds) => void; } export function GoodBadEventsChart({ @@ -39,6 +42,7 @@ export function GoodBadEventsChart({ bottomTitle, data, slo, + onBrushed, isLoading = false, }: Props) { const { charts, uiSettings, discover } = useKibana().services; @@ -97,7 +101,16 @@ export function GoodBadEventsChart({ showLegend={true} showLegendExtra={false} legendPosition={Position.Left} - noResults={} + noResults={ + + } onPointerUpdate={handleCursorUpdate} externalPointerEvents={{ tooltip: { visible: true }, @@ -106,6 +119,9 @@ export function GoodBadEventsChart({ pointerUpdateTrigger={'x'} locale={i18n.getLocale()} onElementClick={barClickHandler as ElementClickListener} + onBrushEnd={(brushArea) => { + onBrushed?.(getBrushData(brushArea)); + }} /> {annotation} { registerSloUsageCollector(plugins.usageCollection); - core.getStartServices().then(([coreStart, pluginStart]) => { - registerRoutes({ - core, - config, - dependencies: { - pluginsSetup: { - ...plugins, - core, - }, - spaces: pluginStart.spaces, - ruleDataService, - getRulesClientWithRequest: pluginStart.alerting.getRulesClientWithRequest, + registerRoutes({ + core, + config, + dependencies: { + pluginsSetup: { + ...plugins, + core, }, - logger: this.logger, - repository: getSloServerRouteRepository({ - isServerless: !!pluginStart.serverless, - }), - }); - - const esInternalClient = coreStart.elasticsearch.client.asInternalUser; - - const sloResourceInstaller = new DefaultResourceInstaller(esInternalClient, this.logger); - const sloInstaller = new DefaultSLOInstaller(sloResourceInstaller, this.logger); - sloInstaller.install(); + getSpacesStart: async () => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.spaces; + }, + ruleDataService, + getRulesClientWithRequest: async (request) => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.alerting.getRulesClientWithRequest(request); + }, + }, + logger: this.logger, + repository: getSloServerRouteRepository({ + isServerless: this.initContext.env.packageInfo.buildFlavor === 'serverless', + }), }); + core + .getStartServices() + .then(async ([coreStart, pluginStart]) => { + const esInternalClient = coreStart.elasticsearch.client.asInternalUser; + const sloResourceInstaller = new DefaultResourceInstaller(esInternalClient, this.logger); + const sloInstaller = new DefaultSLOInstaller(sloResourceInstaller, this.logger); + await sloInstaller.install(); + }) + .catch((error) => { + this.logger.error(`Failed to install the default SLOs: ${error}`); + }); + this.sloOrphanCleanupTask = new SloOrphanSummaryCleanupTask( plugins.taskManager, this.logger, @@ -175,7 +183,9 @@ export class SloPlugin implements Plugin { const internalSoClient = new SavedObjectsClient(core.savedObjects.createInternalRepository()); const internalEsClient = core.elasticsearch.client.asInternalUser; - this.sloOrphanCleanupTask?.start(plugins.taskManager, internalSoClient, internalEsClient); + this.sloOrphanCleanupTask + ?.start(plugins.taskManager, internalSoClient, internalEsClient) + .catch(() => {}); } public stop() {} diff --git a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts index 66f255a2397b7..83f5ce796e70d 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts @@ -34,9 +34,9 @@ export interface RegisterRoutesDependencies { pluginsSetup: { core: CoreSetup; }; - spaces?: SpacesPluginStart; + getSpacesStart: () => Promise; ruleDataService: RuleDataPluginService; - getRulesClientWithRequest: (request: KibanaRequest) => RulesClientApi; + getRulesClientWithRequest: (request: KibanaRequest) => Promise; } export function registerRoutes({ config, repository, core, logger, dependencies }: RegisterRoutes) { diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index 1853d09c81f54..d71a65e95013a 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -98,8 +98,8 @@ const createSLORoute = createSloServerRoute({ handler: async ({ context, params, logger, dependencies, request }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const basePath = dependencies.pluginsSetup.core.http.basePath; @@ -143,8 +143,8 @@ const inspectSLORoute = createSloServerRoute({ handler: async ({ context, params, logger, dependencies, request }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const basePath = dependencies.pluginsSetup.core.http.basePath; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; @@ -185,8 +185,8 @@ const updateSLORoute = createSloServerRoute({ handler: async ({ context, request, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const basePath = dependencies.pluginsSetup.core.http.basePath; const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -231,12 +231,12 @@ const deleteSLORoute = createSloServerRoute({ handler: async ({ request, context, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; - const rulesClient = dependencies.getRulesClientWithRequest(request); + const rulesClient = await dependencies.getRulesClientWithRequest(request); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( @@ -274,8 +274,8 @@ const getSLORoute = createSloServerRoute({ handler: async ({ request, context, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -298,8 +298,8 @@ const enableSLORoute = createSloServerRoute({ handler: async ({ request, context, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -335,8 +335,8 @@ const disableSLORoute = createSloServerRoute({ handler: async ({ request, context, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -372,8 +372,8 @@ const resetSLORoute = createSloServerRoute({ handler: async ({ context, request, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const basePath = dependencies.pluginsSetup.core.http.basePath; @@ -417,8 +417,8 @@ const findSLORoute = createSloServerRoute({ handler: async ({ context, request, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const repository = new KibanaSavedObjectsSLORepository(soClient, logger); @@ -439,8 +439,8 @@ const findSLOGroupsRoute = createSloServerRoute({ params: findSLOGroupsParamsSchema, handler: async ({ context, request, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService.getActiveSpace(request))?.id ?? 'default'; const soClient = (await context.core).savedObjects.client; const coreContext = context.core; const esClient = (await coreContext).elasticsearch.client.asCurrentUser; @@ -592,8 +592,8 @@ const getSloBurnRates = createSloServerRoute({ handler: async ({ request, context, params, logger, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService.getActiveSpace(request))?.id ?? 'default'; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const soClient = (await context.core).savedObjects.client; @@ -624,8 +624,8 @@ const getPreviewData = createSloServerRoute({ handler: async ({ request, context, params, dependencies }) => { await assertPlatinumLicense(context); - const spaceId = - (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + const spaces = await dependencies.getSpacesStart(); + const spaceId = (await spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const service = new GetPreviewData(esClient, spaceId); diff --git a/x-pack/plugins/observability_solution/slo/server/services/burn_rates_client.ts b/x-pack/plugins/observability_solution/slo/server/services/burn_rates_client.ts index 08b1c460bdf2b..ebcf04538886b 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/burn_rates_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/burn_rates_client.ts @@ -19,6 +19,7 @@ import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; +import { getEsDateRange } from './historical_summary_client'; import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants'; import { DateRange, Duration, SLODefinition } from '../domain/models'; import { computeBurnRate, computeSLI } from '../domain/services'; @@ -98,7 +99,7 @@ function commonQuery( { term: { 'slo.revision': slo.revision } }, { range: { - '@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() }, + '@timestamp': getEsDateRange(dateRange), }, }, ]; diff --git a/x-pack/plugins/observability_solution/slo/server/services/historical_summary_client.ts b/x-pack/plugins/observability_solution/slo/server/services/historical_summary_client.ts index 08cd11c73a9a1..6e07bb30c2cb6 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/historical_summary_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/historical_summary_client.ts @@ -64,8 +64,8 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient { async fetch(params: FetchHistoricalSummaryParams): Promise { const dateRangeBySlo = params.list.reduce>( - (acc, { sloId, timeWindow }) => { - acc[sloId] = getDateRange(timeWindow); + (acc, { sloId, timeWindow, range }) => { + acc[sloId] = range ?? getDateRange(timeWindow); return acc; }, {} @@ -272,6 +272,13 @@ function handleResultForRollingAndTimeslices( }); } +export const getEsDateRange = (dateRange: DateRange) => { + return { + gte: typeof dateRange.from === 'string' ? dateRange.from : dateRange.from.toISOString(), + lte: typeof dateRange.to === 'string' ? dateRange.to : dateRange.to.toISOString(), + }; +}; + function generateSearchQuery({ sloId, groupBy, @@ -309,10 +316,7 @@ function generateSearchQuery({ { term: { 'slo.revision': revision } }, { range: { - '@timestamp': { - gte: dateRange.from.toISOString(), - lte: dateRange.to.toISOString(), - }, + '@timestamp': getEsDateRange(dateRange), }, }, ...extraFilterByInstanceId, @@ -325,7 +329,7 @@ function generateSearchQuery({ field: '@timestamp', fixed_interval: fixedInterval, extended_bounds: { - min: dateRange.from.toISOString(), + min: typeof dateRange.from === 'string' ? dateRange.from : dateRange.from.toISOString(), max: 'now/d', }, }, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts index ec735058b1bd3..584aba75e22ff 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts @@ -58,7 +58,7 @@ describe('SummaryClient', () => { { term: { 'slo.revision': slo.revision } }, { range: { - '@timestamp': { gte: expect.anything(), lt: expect.anything() }, + '@timestamp': { gte: expect.anything(), lte: expect.anything() }, }, }, ], @@ -94,7 +94,7 @@ describe('SummaryClient', () => { range: { '@timestamp': { gte: expect.anything(), - lt: expect.anything(), + lte: expect.anything(), }, }, }, @@ -136,7 +136,7 @@ describe('SummaryClient', () => { { term: { 'slo.revision': slo.revision } }, { range: { - '@timestamp': { gte: expect.anything(), lt: expect.anything() }, + '@timestamp': { gte: expect.anything(), lte: expect.anything() }, }, }, ], @@ -188,7 +188,7 @@ describe('SummaryClient', () => { range: { '@timestamp': { gte: expect.anything(), - lt: expect.anything(), + lte: expect.anything(), }, }, }, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts index 4a41db114c9cf..5e5ee5a7228a0 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts @@ -16,6 +16,7 @@ import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; +import { getEsDateRange } from './historical_summary_client'; import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants'; import { Groupings, Meta, SLODefinition, Summary } from '../domain/models'; import { computeSLI, computeSummaryStatus, toErrorBudget } from '../domain/services'; @@ -75,7 +76,7 @@ export class DefaultSummaryClient implements SummaryClient { { term: { 'slo.revision': slo.revision } }, { range: { - '@timestamp': { gte: dateRange.from.toISOString(), lt: dateRange.to.toISOString() }, + '@timestamp': getEsDateRange(dateRange), }, }, ...instanceIdFilter, diff --git a/x-pack/plugins/observability_solution/slo/server/services/tasks/orphan_summary_cleanup_task.ts b/x-pack/plugins/observability_solution/slo/server/services/tasks/orphan_summary_cleanup_task.ts index 1d9a968dc1ff4..bdbb955050358 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/tasks/orphan_summary_cleanup_task.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/tasks/orphan_summary_cleanup_task.ts @@ -232,7 +232,7 @@ export class SloOrphanSummaryCleanupTask { } if (this.config.sloOrphanSummaryCleanUpTaskEnabled) { - this.taskManager.ensureScheduled({ + await this.taskManager.ensureScheduled({ id: this.taskId, taskType: TASK_TYPE, schedule: { @@ -243,7 +243,7 @@ export class SloOrphanSummaryCleanupTask { params: {}, }); } else { - this.taskManager.removeIfExists(this.taskId); + await this.taskManager.removeIfExists(this.taskId); } } } diff --git a/x-pack/plugins/observability_solution/slo/tsconfig.json b/x-pack/plugins/observability_solution/slo/tsconfig.json index b8a9c1d5e21b2..70ff3d26f591f 100644 --- a/x-pack/plugins/observability_solution/slo/tsconfig.json +++ b/x-pack/plugins/observability_solution/slo/tsconfig.json @@ -97,6 +97,7 @@ "@kbn/data-view-field-editor-plugin", "@kbn/securitysolution-io-ts-utils", "@kbn/core-elasticsearch-server-mocks", + "@kbn/datemath", "@kbn/presentation-containers", ] } diff --git a/x-pack/plugins/observability_solution/synthetics/server/lib.ts b/x-pack/plugins/observability_solution/synthetics/server/lib.ts index 6ee70f5282e83..78d88ff6bf51b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/lib.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/lib.ts @@ -65,7 +65,7 @@ export class UptimeEsClient { this.heartbeatIndices = heartbeatIndices; this.isDev = isDev; this.inspectableEsQueries = []; - this.getInspectEnabled(); + this.getInspectEnabled().catch(() => {}); } async initSettings() { @@ -176,15 +176,14 @@ export class UptimeEsClient { return {}; } async getInspectEnabled() { - if (this.isInspectorEnabled !== undefined) { - return this.isInspectorEnabled; - } - if (!this.uiSettings) { return false; } - this.isInspectorEnabled = this.uiSettings.client.get(enableInspectEsQueries); + if (this.isInspectorEnabled === undefined) { + this.isInspectorEnabled = this.uiSettings.client.get(enableInspectEsQueries); + } + return this.isInspectorEnabled; } async getIndices() { diff --git a/x-pack/plugins/observability_solution/synthetics/server/plugin.ts b/x-pack/plugins/observability_solution/synthetics/server/plugin.ts index 76ac2afcf102b..91331dd124cf1 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/plugin.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/plugin.ts @@ -79,7 +79,7 @@ export class Plugin implements PluginType { this.syntheticsService = new SyntheticsService(this.server); - this.syntheticsService.setup(plugins.taskManager); + this.syntheticsService.setup(plugins.taskManager).catch(() => {}); this.syntheticsMonitorClient = new SyntheticsMonitorClient(this.syntheticsService, this.server); @@ -112,7 +112,7 @@ export class Plugin implements PluginType { this.syntheticsService?.start(pluginsStart.taskManager); - this.telemetryEventsSender.start(pluginsStart.telemetry, coreStart); + this.telemetryEventsSender.start(pluginsStart.telemetry, coreStart).catch(() => {}); } public stop() {} diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts index 7093b83dab82f..cd105bd0fd831 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts @@ -259,9 +259,14 @@ export class AddEditMonitorAPI { try { // we do this async, so we don't block the user, error handling will be done on the UI via separate api const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient); - defaultAlertService.setupDefaultAlerts().then(() => { - server.logger.debug(`Successfully created default alert for monitor: ${name}`); - }); + defaultAlertService + .setupDefaultAlerts() + .then(() => { + server.logger.debug(`Successfully created default alert for monitor: ${name}`); + }) + .catch((error) => { + server.logger.error(`Error creating default alert: ${error} for monitor: ${name}`); + }); } catch (e) { server.logger.error(`Error creating default alert: ${e} for monitor: ${name}`); } diff --git a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index b8e9a08bf3dd1..c742bf26176ad 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -50,8 +50,7 @@ export const getAllMonitors = async ({ hits.push(...result.saved_objects); } - // no need to wait for it - finder.close(); + finder.close().catch(() => {}); return hits; }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 418fbde0d31db..29b65def487dc 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -201,7 +201,7 @@ export class SyntheticsPrivateLocation { const result = await this.createPolicyBulk(newPolicies); if (result?.created && result?.created?.length > 0 && testRunId) { // ignore await here, we don't want to wait for this to finish - scheduleCleanUpTask(this.server); + void scheduleCleanUpTask(this.server); } return result; } catch (e) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index 3707b0b9be746..108facf7076a1 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -258,8 +258,7 @@ export class ProjectMonitorFormatter { ); } - // no need to wait for it - finder.close(); + finder.close().catch(() => {}); return hits; }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index f3b006f3bd241..e9fbe8f0ef3b4 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -354,8 +354,7 @@ export class SyntheticsMonitorClient { }); } - // no need to wait here - finder.close(); + finder.close().catch(() => {}); return monitors; } diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts index 60bc4ab9007e0..e7fcff93c8018 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.test.ts @@ -154,9 +154,12 @@ describe('SyntheticsService', () => { it('setup properly', async () => { const service = new SyntheticsService(serverMock); - service.setup(taskManagerSetup); expect(service.isAllowed).toEqual(false); + + await service.setup(taskManagerSetup); + + expect(service.isAllowed).toEqual(true); expect(service.locations).toEqual([]); expect(service.signupUrl).toEqual(null); }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts index fd37aa678ce63..a9e36289b1698 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/synthetics_service.ts @@ -100,9 +100,9 @@ export class SyntheticsService { public start(taskManager: TaskManagerStartContract) { if (this.config?.manifestUrl) { - this.scheduleSyncTask(taskManager); + void this.scheduleSyncTask(taskManager); } - this.setupIndexTemplates(); + void this.setupIndexTemplates(); } public async setupIndexTemplates() { @@ -178,7 +178,7 @@ export class SyntheticsService { service.signupUrl = signupUrl; if (service.isAllowed && service.config.manifestUrl) { - service.setupIndexTemplates(); + void service.setupIndexTemplates(); await service.pushConfigs(); } } catch (e) { @@ -588,7 +588,7 @@ export class SyntheticsService { } // no need to wait here - finder.close(); + finder.close().catch(() => {}); if (paramsBySpace[ALL_SPACES_ID]) { Object.keys(paramsBySpace).forEach((space) => { diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/mocks.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/mocks.ts index b9d6b54961f59..796e92ddbc5b2 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/mocks.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/mocks.ts @@ -24,7 +24,7 @@ export const mockEncryptedSO = ({ createPointInTimeFinderDecryptedAsInternalUser: jest .fn() .mockImplementation(({ perPage, type: soType }) => ({ - close: jest.fn(), + close: jest.fn(async () => {}), find: jest.fn().mockReturnValue({ async *[Symbol.asyncIterator]() { if (soType === syntheticsParamType) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.test.ts b/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.test.ts index ab599ae0d0ea8..91f859545600b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.test.ts @@ -57,7 +57,7 @@ describe('TelemetryEventsSender', () => { revision: 1, }; - beforeEach(() => { + beforeEach(async () => { logger = loggingSystemMock.createLogger(); sender = new TelemetryEventsSender(logger); sender['fetchLicenseInfo'] = jest.fn(async () => { @@ -73,7 +73,7 @@ describe('TelemetryEventsSender', () => { }, } as InfoResponse; }); - sender.start(undefined, { + await sender.start(undefined, { elasticsearch: { client: { asInternalUser: { info: jest.fn(async () => ({})) } } }, } as any); }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.ts b/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.ts index 610f43aa9cfd9..e495181781fe0 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/telemetry/sender.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { exhaustMap, Subject, takeUntil, timer } from 'rxjs'; import type { CoreStart, ElasticsearchClient, Logger } from '@kbn/core/server'; import type { TelemetryPluginStart, TelemetryPluginSetup } from '@kbn/telemetry-plugin/server'; @@ -26,10 +27,10 @@ export class TelemetryEventsSender { private readonly initialCheckDelayMs = 10 * 1000; private readonly checkIntervalMs = 30 * 1000; private readonly logger: Logger; + private readonly stop$ = new Subject(); private telemetryStart?: TelemetryPluginStart; private telemetrySetup?: TelemetryPluginSetup; - private intervalId?: NodeJS.Timeout; private isSending = false; private queuesPerChannel: { [channel: string]: TelemetryQueue } = {}; private isOptedIn?: boolean = true; // Assume true until the first check @@ -52,16 +53,16 @@ export class TelemetryEventsSender { this.licenseInfo = await this.fetchLicenseInfo(); this.logger.debug(`Starting local task`); - setTimeout(() => { - this.sendIfDue(); - this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); - }, this.initialCheckDelayMs); + timer(this.initialCheckDelayMs, this.checkIntervalMs) + .pipe( + takeUntil(this.stop$), + exhaustMap(() => this.sendIfDue()) + ) + .subscribe(); } public stop() { - if (this.intervalId) { - clearInterval(this.intervalId); - } + this.stop$.next(); } public queueTelemetryEvents( diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/lib.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/lib.ts index 15f2530667d7d..5b1326bf7a065 100644 --- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/lib.ts +++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/lib/lib.ts @@ -80,7 +80,7 @@ export class UptimeEsClient { this.heartbeatIndices = heartbeatIndices; this.isDev = isDev; this.inspectableEsQueries = []; - this.getInspectEnabled(); + this.getInspectEnabled().catch(() => {}); } async initSettings() { diff --git a/x-pack/plugins/osquery/server/create_transforms/create_transforms.ts b/x-pack/plugins/osquery/server/create_transforms/create_transforms.ts index b3c1b74967721..b365449c75121 100644 --- a/x-pack/plugins/osquery/server/create_transforms/create_transforms.ts +++ b/x-pack/plugins/osquery/server/create_transforms/create_transforms.ts @@ -21,12 +21,12 @@ export const initializeTransform = async ( esClient: ElasticsearchClient, transform: TransformPutTransformRequest, logger: Logger -) => - createTransformIfNotExists(esClient, transform, logger).then((succeeded) => { - if (succeeded) { - startTransformIfNotStarted(esClient, transform.transform_id, logger); - } - }); +) => { + const succeeded = await createTransformIfNotExists(esClient, transform, logger); + if (succeeded) { + await startTransformIfNotStarted(esClient, transform.transform_id, logger); + } +}; /** * Checks if a transform exists, And if not creates it diff --git a/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts b/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts index 0053cd9523135..4749d36522569 100644 --- a/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts @@ -40,7 +40,7 @@ export class TelemetryReceiver { this.logger = logger.get('telemetry_events'); } - public async start(core: CoreStart, osqueryContextService?: OsqueryAppContextService) { + public start(core: CoreStart, osqueryContextService?: OsqueryAppContextService) { this.agentClient = osqueryContextService?.getAgentService()?.asInternalUser; this.agentPolicyService = osqueryContextService?.getAgentPolicyService(); this.packageService = osqueryContextService?.getPackageService(); diff --git a/x-pack/plugins/osquery/server/lib/update_global_packs.ts b/x-pack/plugins/osquery/server/lib/update_global_packs.ts index 8a3e048532202..2cf48bf59013c 100644 --- a/x-pack/plugins/osquery/server/lib/update_global_packs.ts +++ b/x-pack/plugins/osquery/server/lib/update_global_packs.ts @@ -46,7 +46,7 @@ export const updateGlobalPacksCreateCallback = async ( if (packsContainingShardForPolicy.length) { await Promise.all( - map(packsContainingShardForPolicy, (pack) => { + map(packsContainingShardForPolicy, (pack) => packsClient.update( packSavedObjectType, pack.saved_object_id, @@ -61,8 +61,8 @@ export const updateGlobalPacksCreateCallback = async ( }, ], } - ); - }) + ) + ) ); return produce(packagePolicy, (draft) => { diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts index 117feb69fa4b9..9a0772708d133 100644 --- a/x-pack/plugins/osquery/server/plugin.ts +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -80,15 +80,20 @@ export class OsqueryPlugin implements Plugin { - const osquerySearchStrategy = osquerySearchStrategyProvider( - depsStart.data, - elasticsearch.client - ); - - plugins.data.search.registerSearchStrategy('osquerySearchStrategy', osquerySearchStrategy); - defineRoutes(router, osqueryContext); - }); + core + .getStartServices() + .then(([{ elasticsearch }, depsStart]) => { + const osquerySearchStrategy = osquerySearchStrategyProvider( + depsStart.data, + elasticsearch.client + ); + + plugins.data.search.registerSearchStrategy('osquerySearchStrategy', osquerySearchStrategy); + defineRoutes(router, osqueryContext); + }) + .catch(() => { + // it shouldn't reject, but just in case + }); this.telemetryEventsSender.setup(this.telemetryReceiver, plugins.taskManager, core.analytics); @@ -117,64 +122,71 @@ export class OsqueryPlugin implements Plugin { - const packageInfo = await plugins.fleet?.packageService.asInternalUser.getInstallation( - OSQUERY_INTEGRATION_NAME - ); - const client = new SavedObjectsClient(core.savedObjects.createInternalRepository()); - - const esClient = core.elasticsearch.client.asInternalUser; - const dataViewsService = await plugins.dataViews.dataViewsServiceFactory( - client, - esClient, - undefined, - true - ); - - // If package is installed we want to make sure all needed assets are installed - if (packageInfo) { - await this.initialize(core, dataViewsService); - } - - // Upgrade integration into 1.6.0 and rollover if found 'generic' dataset - we do not want to wait for it - upgradeIntegration({ packageInfo, client, esClient, logger: this.logger }); - - if (registerIngestCallback) { - registerIngestCallback( - 'packagePolicyCreate', - async (newPackagePolicy: NewPackagePolicy): Promise => { - if (newPackagePolicy.package?.name === OSQUERY_INTEGRATION_NAME) { - await this.initialize(core, dataViewsService); - - const allPacks = await client - .find({ - type: packSavedObjectType, - }) - .then((data) => ({ - ...data, - saved_objects: data.saved_objects.map((pack) => ({ - ...pack.attributes, - saved_object_id: pack.id, - })), - })); - - if (allPacks.saved_objects) { - return updateGlobalPacksCreateCallback( - newPackagePolicy, - client, - allPacks.saved_objects, - this.osqueryAppContextService - ); - } - } - - return newPackagePolicy; - } + plugins.fleet + ?.fleetSetupCompleted() + .then(async () => { + const packageInfo = await plugins.fleet?.packageService.asInternalUser.getInstallation( + OSQUERY_INTEGRATION_NAME + ); + const client = new SavedObjectsClient(core.savedObjects.createInternalRepository()); + + const esClient = core.elasticsearch.client.asInternalUser; + const dataViewsService = await plugins.dataViews.dataViewsServiceFactory( + client, + esClient, + undefined, + true ); - registerIngestCallback('packagePolicyPostDelete', getPackagePolicyDeleteCallback(client)); - } - }); + // If package is installed we want to make sure all needed assets are installed + if (packageInfo) { + await this.initialize(core, dataViewsService); + } + + // Upgrade integration into 1.6.0 and rollover if found 'generic' dataset - we do not want to wait for it + upgradeIntegration({ packageInfo, client, esClient, logger: this.logger }).catch(() => { + // we do not want to wait for it + }); + + if (registerIngestCallback) { + registerIngestCallback( + 'packagePolicyCreate', + async (newPackagePolicy: NewPackagePolicy): Promise => { + if (newPackagePolicy.package?.name === OSQUERY_INTEGRATION_NAME) { + await this.initialize(core, dataViewsService); + + const allPacks = await client + .find({ + type: packSavedObjectType, + }) + .then((data) => ({ + ...data, + saved_objects: data.saved_objects.map((pack) => ({ + ...pack.attributes, + saved_object_id: pack.id, + })), + })); + + if (allPacks.saved_objects) { + return updateGlobalPacksCreateCallback( + newPackagePolicy, + client, + allPacks.saved_objects, + this.osqueryAppContextService + ); + } + } + + return newPackagePolicy; + } + ); + + registerIngestCallback('packagePolicyPostDelete', getPackagePolicyDeleteCallback(client)); + } + }) + .catch(() => { + // it shouldn't reject, but just in case + }); return {}; } diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index eef04ad477c45..946ceb4f105b0 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -75,7 +75,7 @@ describe('ReportingStore', () => { payload: {}, meta: {}, } as any); - expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( + await expect(store.addReport(mockReport)).rejects.toMatchInlineSnapshot( `[Error: Report object from ES has missing fields!]` ); }); diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 8cb6df23ae766..2a430c45b54d8 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -119,20 +119,23 @@ export class RuleRegistryPlugin this.ruleDataService.initializeService(); - core.getStartServices().then(([_, depsStart]) => { - const ruleRegistrySearchStrategy = ruleRegistrySearchStrategyProvider( - depsStart.data, - depsStart.alerting, - logger, - plugins.security, - depsStart.spaces - ); - - plugins.data.search.registerSearchStrategy( - RULE_SEARCH_STRATEGY_NAME, - ruleRegistrySearchStrategy - ); - }); + core + .getStartServices() + .then(([_, depsStart]) => { + const ruleRegistrySearchStrategy = ruleRegistrySearchStrategyProvider( + depsStart.data, + depsStart.alerting, + logger, + plugins.security, + depsStart.spaces + ); + + plugins.data.search.registerSearchStrategy( + RULE_SEARCH_STRATEGY_NAME, + ruleRegistrySearchStrategy + ); + }) + .catch(() => {}); // ALERTS ROUTES const router = core.http.createRouter(); diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts index defaed0a7500b..f2cbe8ef5aeb7 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.test.ts @@ -88,11 +88,11 @@ describe('resourceInstaller', () => { pluginStop$, dataStreamAdapter, }); - installer.installCommonResources(); + await installer.installCommonResources(); expect(getClusterClient).not.toHaveBeenCalled(); }); - it('should not install index level resources', () => { + it('should not install index level resources', async () => { const mockClusterClient = elasticsearchServiceMock.createElasticsearchClient(); const getClusterClient = jest.fn(() => Promise.resolve(mockClusterClient)); @@ -119,7 +119,7 @@ describe('resourceInstaller', () => { }; const indexInfo = new IndexInfo({ indexOptions, kibanaVersion: '8.1.0' }); - installer.installIndexLevelResources(indexInfo); + await installer.installIndexLevelResources(indexInfo); expect(mockClusterClient.cluster.putComponentTemplate).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts index 99454bfe30946..49e7d706b3117 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts @@ -199,8 +199,8 @@ export const ruleRegistrySearchStrategyProvider = ( ); }, cancel: async (id, options, deps) => { - if (internalUserEs.cancel) internalUserEs.cancel(id, options, deps); - if (requestUserEs.cancel) requestUserEs.cancel(id, options, deps); + if (internalUserEs.cancel) internalUserEs.cancel(id, options, deps).catch(() => {}); + if (requestUserEs.cancel) requestUserEs.cancel(id, options, deps).catch(() => {}); }, }; }; diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts index 5727a854ac3ca..fbe118280fe85 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts @@ -386,7 +386,7 @@ export class HeadlessChromiumDriver { errorReason: 'Aborted', requestId, }); - this.page.browser().close(); + void this.page.browser().close(); const error = getDisallowedOutgoingUrlError(interceptedUrl); this.screenshottingErrorSubject.next(error); logger.error(error); @@ -438,7 +438,7 @@ export class HeadlessChromiumDriver { } if (!allowed || !this.allowRequest(interceptedUrl)) { - this.page.browser().close(); + void this.page.browser().close(); const error = getDisallowedOutgoingUrlError(interceptedUrl); this.screenshottingErrorSubject.next(error); logger.error(error); @@ -464,7 +464,7 @@ export class HeadlessChromiumDriver { const wsEndpoint = this.page.browser().wsEndpoint(); const { port } = parseUrl(wsEndpoint); - open( + await open( `http://localhost:${port}/devtools/inspector.html?ws=localhost:${port}/devtools/page/${targetId}` ); } diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts index 317614e7733d1..13b9070edb7fb 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts @@ -82,7 +82,7 @@ describe('HeadlessChromiumDriverFactory', () => { it('rejects if Puppeteer launch fails', async () => { jest.spyOn(puppeteer, 'launch').mockRejectedValue(`Puppeteer Launch mock fail.`); - expect(() => + await expect(() => factory .createPage({ openUrlTimeout: 0, defaultViewport: DEFAULT_VIEWPORT }) .pipe(take(1)) diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts index 1c562a481790e..66a4dac0c37a2 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts @@ -226,7 +226,7 @@ export class HeadlessChromiumDriverFactory { observer.add(() => { if (page.isClosed()) return; // avoid emitting a log unnecessarily logger.debug(`It looks like the browser is no longer being used. Closing the browser...`); - childProcess.kill(); // ignore async + void childProcess.kill(); // ignore async }); // make the observer subscribe to terminate$ @@ -272,7 +272,7 @@ export class HeadlessChromiumDriverFactory { logger.error(error); }); }); - })(); + })().catch(() => {}); }); } diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/index.ts b/x-pack/plugins/screenshotting/server/formats/pdf/index.ts index 8a4571c128f9c..ff58fbf6d8bc5 100644 --- a/x-pack/plugins/screenshotting/server/formats/pdf/index.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/index.ts @@ -130,9 +130,9 @@ export async function toPdf( } } else { buffer = results[0].screenshots[0].data; // This buffer is already the PDF - pages = await PDFJS.getDocument({ data: buffer }).promise.then((doc) => { + pages = await PDFJS.getDocument({ data: buffer }).promise.then(async (doc) => { const numPages = doc.numPages; - doc.destroy(); + await doc.destroy(); return numPages; }); } diff --git a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts index 13e79c4ed3422..0c6e4874b4bf1 100644 --- a/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts +++ b/x-pack/plugins/search_notebooks/server/lib/notebook_catalog.test.ts @@ -25,24 +25,24 @@ describe('getNotebook', () => { jest.clearAllMocks(); }); - it('throws an error if given an unknown notebook id', () => { - expect(getNotebook('some-fake-id', options)).rejects.toThrow('Unknown Notebook ID'); + it('throws an error if given an unknown notebook id', async () => { + await expect(getNotebook('some-fake-id', options)).rejects.toThrow('Unknown Notebook ID'); expect(mockLogger.warn).toHaveBeenCalledTimes(1); }); - it('throws an error if the file is not found', () => { + it('throws an error if the file is not found', async () => { const notebookId = DEFAULT_NOTEBOOKS.notebooks[0].id; jest.mocked(fs.access).mockReset().mockRejectedValue(new Error('Boom')); - expect(getNotebook(notebookId, options)).rejects.toThrow('Failed to fetch notebook.'); + await expect(getNotebook(notebookId, options)).rejects.toThrow('Failed to fetch notebook.'); }); - it('Reads notebook', () => { + it('Reads notebook', async () => { const notebookId = DEFAULT_NOTEBOOKS.notebooks[0].id; jest.mocked(fs.access).mockReset().mockResolvedValue(undefined); - expect(getNotebook(notebookId, options)).resolves.toMatchObject({ + await expect(getNotebook(notebookId, options)).resolves.toMatchObject({ cells: expect.anything(), metadata: expect.anything(), }); diff --git a/x-pack/plugins/search_playground/__mocks__/router.mock.ts b/x-pack/plugins/search_playground/__mocks__/router.mock.ts new file mode 100644 index 0000000000000..328f44997b757 --- /dev/null +++ b/x-pack/plugins/search_playground/__mocks__/router.mock.ts @@ -0,0 +1,95 @@ +/* + * 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 { + IRouter, + KibanaRequest, + RequestHandlerContext, + RouteValidatorConfig, +} from '@kbn/core/server'; +import { httpServiceMock, httpServerMock } from '@kbn/core/server/mocks'; + +/** + * Test helper that mocks Kibana's router and DRYs out various helper (callRoute, schema validation) + */ + +type MethodType = 'get' | 'post' | 'put' | 'patch' | 'delete'; +type PayloadType = 'params' | 'query' | 'body'; + +interface IMockRouter { + method: MethodType; + path: string; + context?: jest.Mocked; +} +interface IMockRouterRequest { + body?: object; + query?: object; + params?: object; +} +type MockRouterRequest = KibanaRequest | IMockRouterRequest; + +export class MockRouter { + public router!: jest.Mocked; + public method: MethodType; + public path: string; + public context: jest.Mocked; + public payload?: PayloadType; + public response = httpServerMock.createResponseFactory(); + + constructor({ method, path, context = {} as jest.Mocked }: IMockRouter) { + this.createRouter(); + this.method = method; + this.path = path; + this.context = context; + } + + public createRouter = () => { + this.router = httpServiceMock.createRouter(); + }; + + public callRoute = async (request: MockRouterRequest) => { + const route = this.findRouteRegistration(); + const [, handler] = route; + await handler(this.context, httpServerMock.createKibanaRequest(request as any), this.response); + }; + + /** + * Schema validation helpers + */ + + public validateRoute = (request: MockRouterRequest) => { + const route = this.findRouteRegistration(); + const [config] = route; + const validate = config.validate as RouteValidatorConfig<{}, {}, {}>; + const payloads = Object.keys(request) as PayloadType[]; + + payloads.forEach((payload: PayloadType) => { + const payloadValidation = validate[payload] as { validate(request: KibanaRequest): void }; + const payloadRequest = request[payload] as KibanaRequest; + + payloadValidation.validate(payloadRequest); + }); + }; + + public shouldValidate = (request: MockRouterRequest) => { + expect(() => this.validateRoute(request)).not.toThrow(); + }; + + public shouldThrow = (request: MockRouterRequest) => { + expect(() => this.validateRoute(request)).toThrow(); + }; + + private findRouteRegistration = () => { + const routerCalls = this.router[this.method].mock.calls as any[]; + if (!routerCalls.length) throw new Error('No routes registered.'); + + const route = routerCalls.find(([router]: any) => router.path === this.path); + if (!route) throw new Error('No matching registered routes found - check method/path keys'); + + return route; + }; +} diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts index bb65f55928bcd..58ce10a3b8051 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts @@ -76,7 +76,7 @@ describe('conversational chain', () => { const stream = await conversationalChain.stream(aiClient, chat); - const streamToValue: string[] = await new Promise((resolve) => { + const streamToValue: string[] = await new Promise((resolve, reject) => { const reader = stream.getReader(); const textDecoder = new TextDecoder(); const chunks: string[] = []; @@ -89,7 +89,7 @@ describe('conversational chain', () => { chunks.push(textDecoder.decode(value)); read(); } - }); + }, reject); }; read(); }); diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts index 0844fe3b64c42..b18faa06780e8 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts @@ -167,7 +167,7 @@ class ConversationalChainFn { // check that main chain (without parent) is finished: if (parentRunId == null) { - data.close(); + data.close().catch(() => {}); } }, }, diff --git a/x-pack/plugins/search_playground/server/routes.test.ts b/x-pack/plugins/search_playground/server/routes.test.ts index b149fde95c1e4..b845f99576e61 100644 --- a/x-pack/plugins/search_playground/server/routes.test.ts +++ b/x-pack/plugins/search_playground/server/routes.test.ts @@ -5,7 +5,19 @@ * 2.0. */ -import { createRetriever } from './routes'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { RequestHandlerContext } from '@kbn/core/server'; +import { coreMock } from '@kbn/core/server/mocks'; +import { MockRouter } from '../__mocks__/router.mock'; +import { ConversationalChain } from './lib/conversational_chain'; +import { getChatParams } from './lib/get_chat_params'; +import { createRetriever, defineRoutes } from './routes'; + +jest.mock('./lib/get_chat_params', () => ({ + getChatParams: jest.fn(), +})); + +jest.mock('./lib/conversational_chain'); describe('createRetriever', () => { test('works when the question has quotes', () => { @@ -18,3 +30,75 @@ describe('createRetriever', () => { expect(result).toEqual({ query: { match: { text: 'How can I "do something" with quotes?' } } }); }); }); + +describe('Search Playground routes', () => { + let mockRouter: MockRouter; + const mockClient = { + asCurrentUser: {}, + }; + + const mockCore = { + elasticsearch: { client: mockClient }, + }; + const mockLogger = loggingSystemMock.createLogger().get(); + + describe('POST - Chat Messages', () => { + const mockData = { + connector_id: 'open-ai', + indices: 'my-index', + prompt: 'You are an assistant', + citations: true, + elasticsearch_query: {}, + summarization_model: 'GPT-4', + doc_size: 3, + source_fields: '{}', + }; + + const mockRequestBody = { + data: mockData, + }; + + beforeEach(() => { + jest.clearAllMocks(); + + const coreStart = coreMock.createStart(); + + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'post', + path: '/internal/search_playground/chat', + }); + + defineRoutes({ + logger: mockLogger, + router: mockRouter.router, + getStartServices: jest.fn().mockResolvedValue([coreStart, {}, {}]), + }); + }); + + it('responds with error message if stream throws an error', async () => { + (getChatParams as jest.Mock).mockResolvedValue({ model: 'open-ai' }); + (ConversationalChain as jest.Mock).mockImplementation(() => { + return { + stream: jest + .fn() + .mockRejectedValue(new Error('Unexpected API error - Some Open AI error message')), + }; + }); + + await mockRouter.callRoute({ + body: mockRequestBody, + }); + + expect(mockRouter.response.badRequest).toHaveBeenCalledWith({ + body: { + message: 'Unexpected API error - Some Open AI error message', + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts index 12ba2e89c66cd..d2081dade0474 100644 --- a/x-pack/plugins/search_playground/server/routes.ts +++ b/x-pack/plugins/search_playground/server/routes.ts @@ -131,10 +131,10 @@ export function defineRoutes({ } catch (e) { logger.error('Failed to create the chat stream', e); - if (typeof e === 'string') { + if (typeof e === 'object') { return response.badRequest({ body: { - message: e, + message: e.message, }, }); } @@ -147,15 +147,18 @@ export function defineRoutes({ const reader = (stream as ReadableStream).getReader(); const textDecoder = new TextDecoder(); - async function pushStreamUpdate() { - reader.read().then(({ done, value }: { done: boolean; value?: Uint8Array }) => { - if (done) { - end(); - return; - } - push(textDecoder.decode(value)); - pushStreamUpdate(); - }); + function pushStreamUpdate() { + reader + .read() + .then(({ done, value }: { done: boolean; value?: Uint8Array }) => { + if (done) { + end(); + return; + } + push(textDecoder.decode(value)); + pushStreamUpdate(); + }) + .catch(() => {}); } pushStreamUpdate(); diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json index 7a7ee54279017..4726066210d8f 100644 --- a/x-pack/plugins/search_playground/tsconfig.json +++ b/x-pack/plugins/search_playground/tsconfig.json @@ -35,7 +35,8 @@ "@kbn/elastic-assistant-common", "@kbn/logging", "@kbn/react-kibana-context-render", - "@kbn/doc-links" + "@kbn/doc-links", + "@kbn/core-logging-server-mocks" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts b/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts index e83924f366b91..9477fa8be9084 100644 --- a/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts +++ b/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts @@ -18,7 +18,10 @@ import { getDetailedErrorMessage } from '../../errors'; import { PrivilegeSerializer } from '../privilege_serializer'; import { ResourceSerializer } from '../resource_serializer'; -export type ElasticsearchRole = Pick & { +export type ElasticsearchRole = Pick< + Role, + 'name' | 'description' | 'metadata' | 'transient_metadata' +> & { applications: Array<{ application: string; privileges: string[]; @@ -48,6 +51,7 @@ export function transformElasticsearchRoleToRole( ); return { name, + ...(elasticsearchRole.description && { description: elasticsearchRole.description }), metadata: elasticsearchRole.metadata, transient_metadata: elasticsearchRole.transient_metadata, elasticsearch: { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index ab938ac24d30e..b09743fd077e2 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -123,6 +123,7 @@ describe('GET role', () => { name: 'first_role', apiResponse: () => ({ first_role: { + description: 'roleDescription', cluster: ['manage_watcher'], indices: [ { @@ -144,6 +145,7 @@ describe('GET role', () => { statusCode: 200, result: { name: 'first_role', + description: 'roleDescription', metadata: { _reserved: true, }, @@ -174,6 +176,7 @@ describe('GET role', () => { name: 'first_role', apiResponse: () => ({ first_role: { + description: 'roleDescription', cluster: [], indices: [], applications: [ @@ -196,6 +199,7 @@ describe('GET role', () => { statusCode: 200, result: { name: 'first_role', + description: 'roleDescription', metadata: { _reserved: true, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 8a8b688fd9bb5..f66a21e203481 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -28,12 +28,14 @@ export function defineGetRolesRoutes({ createLicensedRouteHandler(async (context, request, response) => { try { const esClient = (await context.core).elasticsearch.client; + const [features, elasticsearchRoles] = await Promise.all([ getFeatures(), await esClient.asCurrentUser.security.getRole({ name: request.params.name, }), ]); + const elasticsearchRole = elasticsearchRoles[request.params.name]; if (elasticsearchRole) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 3823b34f9c153..3fe91ded3342d 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -118,6 +118,7 @@ describe('GET all roles', () => { getRolesTest(`transforms elasticsearch privileges`, { apiResponse: () => ({ first_role: { + description: 'roleDescription', cluster: ['manage_watcher'], indices: [ { @@ -140,6 +141,7 @@ describe('GET all roles', () => { result: [ { name: 'first_role', + description: 'roleDescription', metadata: { _reserved: true, }, @@ -170,6 +172,7 @@ describe('GET all roles', () => { { apiResponse: () => ({ first_role: { + description: 'roleDescription', cluster: [], indices: [], applications: [ @@ -193,6 +196,7 @@ describe('GET all roles', () => { result: [ { name: 'first_role', + description: 'roleDescription', metadata: { _reserved: true, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts index 1bb63f7deca61..4fd330cae2af8 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all_by_space.test.ts @@ -171,6 +171,7 @@ describe('GET all roles by space id', () => { getRolesTest(`returns roles for matching space`, { apiResponse: () => ({ first_role: { + description: 'first role description', cluster: [], indices: [], applications: [ @@ -218,6 +219,7 @@ describe('GET all roles by space id', () => { result: [ { name: 'first_role', + description: 'first role description', metadata: { _reserved: true, }, @@ -251,6 +253,7 @@ describe('GET all roles by space id', () => { getRolesTest(`returns roles with access to all spaces`, { apiResponse: () => ({ first_role: { + description: 'first role description', cluster: [], indices: [], applications: [ @@ -292,6 +295,7 @@ describe('GET all roles by space id', () => { result: [ { name: 'first_role', + description: 'first role description', metadata: { _reserved: true, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index 6bd8e5a4ec70a..9d0a82c1e6ac8 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -31,6 +31,7 @@ export const transformPutPayloadToElasticsearchRole = ( ); return { + ...(rolePayload.description && { description: rolePayload.description }), metadata: rolePayload.metadata, cluster: elasticsearch.cluster || [], indices: elasticsearch.indices || [], @@ -47,6 +48,11 @@ export function getPutPayloadSchema( getBasePrivilegeNames: () => { global: string[]; space: string[] } ) { return schema.object({ + /** + * Optional text to describe the Role + */ + description: schema.maybe(schema.string({ maxLength: 2048 })), + /** * An optional meta-data dictionary. Within the metadata, keys that begin with _ are reserved * for system usage. diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 18a07bce0a23e..4591bfaaea4aa 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -464,6 +464,7 @@ describe('PUT role', () => { putRoleTest(`creates role with everything`, { name: 'foo-role', payload: { + description: 'test description', metadata: { foo: 'test-metadata', }, @@ -540,6 +541,7 @@ describe('PUT role', () => { }, ], cluster: ['test-cluster-privilege'], + description: 'test description', indices: [ { field_security: { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 906141e9c616b..11a910f1565f7 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -62,12 +62,14 @@ export function definePutRolesRoutes({ const { createOnly } = request.query; try { const esClient = (await context.core).elasticsearch.client; + const [features, rawRoles] = await Promise.all([ getFeatures(), esClient.asCurrentUser.security.getRole({ name: request.params.name }, { ignore: [404] }), ]); const { validationErrors } = validateKibanaPrivileges(features, request.body.kibana); + if (validationErrors.length) { return response.badRequest({ body: { diff --git a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts index 22c219a5668ab..821a90dbdeb22 100644 --- a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts +++ b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts @@ -79,7 +79,7 @@ describe('ensureAuthorized', () => { test('throws an error when privilege check fails', async () => { const deps = setupDependencies(); deps.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error('Oh no!')); - expect(ensureAuthorized(deps, [], [], [])).rejects.toThrowError('Oh no!'); + await expect(ensureAuthorized(deps, [], [], [])).rejects.toThrowError('Oh no!'); }); describe('fully authorized', () => { @@ -149,7 +149,7 @@ describe('ensureAuthorized', () => { test('with default options', async () => { const deps = setupDependencies(); deps.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue(resolvedPrivileges); - expect( + await expect( ensureAuthorized(deps, types, actions, namespaces) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Unable to (bar a),(bar b),(bar c),(foo c)"`); }); @@ -199,7 +199,7 @@ describe('ensureAuthorized', () => { test('with default options', async () => { const deps = setupDependencies(); deps.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue(resolvedPrivileges); - expect( + await expect( ensureAuthorized(deps, types, actions, namespaces) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to (bar a),(bar b),(bar c),(foo a),(foo b),(foo c)"` diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 3ad99b95905bb..921628ecbb0d7 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -260,7 +260,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables the new modal for the value list items */ - valueListItemsModalEnabled: false, + valueListItemsModalEnabled: true, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts index 162fe94ecad70..889d74a1c6503 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts @@ -17,6 +17,19 @@ describe('computeHasMetadataOperator', () => { expect(computeHasMetadataOperator('from test* | eval x="[metadata _id]"')).toBe(false); }); it('should be true if query has operator', () => { + expect(computeHasMetadataOperator('from test* metadata _id')).toBe(true); + expect(computeHasMetadataOperator('from test* metadata _id, _index')).toBe(true); + expect(computeHasMetadataOperator('from test* metadata _index, _id')).toBe(true); + expect(computeHasMetadataOperator('from test* metadata _id ')).toBe(true); + expect(computeHasMetadataOperator('from test* metadata _id | limit 10')).toBe(true); + expect( + computeHasMetadataOperator(`from packetbeat* metadata + + _id + | limit 100`) + ).toBe(true); + + // still validates deprecated square bracket syntax expect(computeHasMetadataOperator('from test* [metadata _id]')).toBe(true); expect(computeHasMetadataOperator('from test* [metadata _id, _index]')).toBe(true); expect(computeHasMetadataOperator('from test* [metadata _index, _id]')).toBe(true); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts index 9cadae108ac22..e7a6e523965b2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts @@ -36,10 +36,10 @@ const constructValidationError = (error: Error) => { }; /** - * checks whether query has [metadata _id] operator + * checks whether query has metadata _id operator */ export const computeHasMetadataOperator = (esqlQuery: string) => { - return /(? export const ESQL_VALIDATION_MISSING_ID_IN_QUERY_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.esqlValidation.missingIdInQueryError', { - defaultMessage: `Queries that don’t use the STATS...BY function (non-aggregating queries) must include the [metadata _id, _version, _index] operator after the source command. For example: FROM logs* [metadata _id, _version, _index]. In addition, the metadata properties (_id, _version, and _index) must be returned in the query response.`, + defaultMessage: `Queries that don’t use the STATS...BY function (non-aggregating queries) must include the "metadata _id, _version, _index" operator after the source command. For example: FROM logs* metadata _id, _version, _index. In addition, the metadata properties (_id, _version, and _index) must be returned in the query response.`, } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts index cde92602611ac..dc4394be257e5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useEsqlIndex } from './use_esql_index'; -const validEsqlQuery = 'from auditbeat* [metadata _id, _index, _version]'; +const validEsqlQuery = 'from auditbeat* metadata _id, _index, _version'; describe('useEsqlIndex', () => { it('should return empty array if isQueryReadEnabled is undefined', () => { const { result } = renderHook(() => useEsqlIndex(validEsqlQuery, 'esql', undefined)); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts index 4819f87d5a41f..6a159f87d89d8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_investigation_fields.test.ts @@ -28,7 +28,7 @@ const fetchFieldsFromESQLMock = fetchFieldsFromESQL as jest.Mock; const { wrapper } = createQueryWrapperMock(); -const mockEsqlQuery = 'from auditbeat* [metadata _id]'; +const mockEsqlQuery = 'from auditbeat* metadata _id'; const mockIndexPatternFields: DataViewFieldBase[] = [ { name: 'agent.name', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts index f16858edf0da0..0579dc6fc93cf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts @@ -4,8 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { UseQueryOptions } from '@tanstack/react-query'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import type { RuleResponse } from '../../../../common/api/detection_engine'; import { useFetchRuleByIdQuery } from '../api/hooks/use_fetch_rule_by_id_query'; import * as i18n from './translations'; @@ -13,19 +14,27 @@ import * as i18n from './translations'; * Hook for using to get a Rule from the Detection Engine API * * @param id desired Rule ID's (not rule_id) - * + * @param showToast whether to show toasts on error + * @param options options for the useQuery */ -export const useRule = (id: string, showToast = true) => { +export const useRule = ( + id: string, + showToast = true, + options: UseQueryOptions = {} +) => { const { addError } = useAppToasts(); + let fetchRuleOptions = { + ...options, + }; + + if (showToast) { + fetchRuleOptions = { + ...fetchRuleOptions, + onError: (error) => { + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); + }, + }; + } - return useFetchRuleByIdQuery( - id, - showToast - ? { - onError: (error) => { - addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); - }, - } - : undefined - ); + return useFetchRuleByIdQuery(id, fetchRuleOptions); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts index 4df58eb3f70f4..aa423f859f754 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts @@ -78,7 +78,14 @@ const buildLastAlertQuery = (ruleId: string) => ({ * In that case, try to fetch the latest alert generated by the rule and retrieve the rule data from the alert (fallback). */ export const useRuleWithFallback = (ruleId: string): UseRuleWithFallback => { - const { isFetching: ruleLoading, data: ruleData, error, refetch } = useRule(ruleId, false); + const { + isFetching: ruleLoading, + data: ruleData, + error, + refetch, + } = useRule(ruleId, false, { + refetchOnWindowFocus: false, + }); const { addError } = useAppToasts(); const isExistingRule = !isNotFoundError(error); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 2aa984314bd78..33ef9ad005881 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -431,6 +431,8 @@ export const AddExceptionFlyoutWrapper: React.FC enrichedAlert == null || (memoRuleIndices == null && memoDataViewId == null); + if (isLoading || isRuleLoading) return null; + return ( ({ @@ -112,9 +113,33 @@ describe('#useResponderActionData', () => { }); describe('when agentType is `sentinel_one`', () => { + const createEventDataMock = (): TimelineEventsDetailsItem[] => { + return [ + { + category: 'observer', + field: 'observer.serial_number', + values: ['c06d63d9-9fa2-046d-e91e-dc94cf6695d8'], + originalValue: ['c06d63d9-9fa2-046d-e91e-dc94cf6695d8'], + isObjectArray: false, + }, + ]; + }; + it('should return `responder` menu item as `disabled` if agentType is `sentinel_one` and feature flag is disabled', () => { useIsExperimentalFeatureEnabledMock.mockReturnValue(false); + const { result } = renderHook(() => + useResponderActionData({ + endpointId: 'sentinel-one-id', + agentType: 'sentinel_one', + eventData: createEventDataMock(), + }) + ); + expect(result.current.isDisabled).toEqual(true); + }); + + it('should return responder menu item as disabled with tooltip if agent id property is missing from event data', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(true); const { result } = renderHook(() => useResponderActionData({ endpointId: 'sentinel-one-id', @@ -123,6 +148,9 @@ describe('#useResponderActionData', () => { }) ); expect(result.current.isDisabled).toEqual(true); + expect(result.current.tooltip).toEqual( + 'Event data missing SentinelOne agent identifier (observer.serial_number)' + ); }); it('should return `responder` menu item as `enabled `if agentType is `sentinel_one` and feature flag is enabled', () => { @@ -131,7 +159,7 @@ describe('#useResponderActionData', () => { useResponderActionData({ endpointId: 'sentinel-one-id', agentType: 'sentinel_one', - eventData: [], + eventData: createEventDataMock(), }) ); expect(result.current.isDisabled).toEqual(false); diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts index 5e17f3178d59c..bf31ad493bbbc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts @@ -21,6 +21,7 @@ import { LOADING_ENDPOINT_DATA_TOOLTIP, METADATA_API_ERROR_TOOLTIP, NOT_FROM_ENDPOINT_HOST_TOOLTIP, + SENTINEL_ONE_AGENT_ID_PROPERTY_MISSING, } from './translations'; import { getFieldValue } from '../host_isolation/helpers'; @@ -78,7 +79,6 @@ export const useResponderActionData = ({ tooltip: ReactNode; } => { const isEndpointHost = agentType === 'endpoint'; - const isSentinelOneHost = agentType === 'sentinel_one'; const showResponseActionsConsole = useWithShowResponder(); const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( @@ -93,7 +93,22 @@ export const useResponderActionData = ({ const [isDisabled, tooltip]: [disabled: boolean, tooltip: ReactNode] = useMemo(() => { // v8.13 disabled for third-party agent alerts if the feature flag is not enabled if (!isEndpointHost) { - return [isSentinelOneHost ? !isSentinelOneV1Enabled : true, undefined]; + switch (agentType) { + case 'sentinel_one': + // Disable it if feature flag is disabled + if (!isSentinelOneV1Enabled) { + return [true, undefined]; + } + // Event must have the property that identifies the agent id + if (!getSentinelOneAgentId(eventData ?? null)) { + return [true, SENTINEL_ONE_AGENT_ID_PROPERTY_MISSING]; + } + + return [false, undefined]; + + default: + return [true, undefined]; + } } // Still loading host info @@ -125,11 +140,12 @@ export const useResponderActionData = ({ return [false, undefined]; }, [ isEndpointHost, - isSentinelOneHost, - isSentinelOneV1Enabled, isFetching, error, hostInfo?.host_status, + agentType, + isSentinelOneV1Enabled, + eventData, ]); const handleResponseActionsClick = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx index 4466c2e31df46..cda6113ded653 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx @@ -59,6 +59,7 @@ export const getEntityTableColumns = ( idPrefix={contextID ? `entityTable-${contextID}` : 'entityTable'} isDraggable={isDraggable} sourcererScopeId={getSourcererScopeId(scopeId)} + scopeId={scopeId} render={renderField} data-test-subj="entity-table-value" /> diff --git a/x-pack/plugins/security_solution/public/management/common/translations.ts b/x-pack/plugins/security_solution/public/management/common/translations.ts index 5aa3b52e9d7fd..c1d05301afd6c 100644 --- a/x-pack/plugins/security_solution/public/management/common/translations.ts +++ b/x-pack/plugins/security_solution/public/management/common/translations.ts @@ -194,24 +194,27 @@ export const CONSOLE_COMMANDS = { }, }; -export const CONFIRM_WARNING_MODAL_LABELS = { - title: i18n.translate('xpack.securitySolution.artifacts.confirmWarningModal.title', { - defaultMessage: 'Confirm trusted application', - }), - body: i18n.translate('xpack.securitySolution.artifacts.confirmWarningModal.body', { - defaultMessage: - 'Using a "*" or a "?" in the value with the "IS" operator can make the entry ineffective. Change the operator to ‘matches’ to ensure wildcards run properly. Select “cancel” to revise your entry, or "add" to continue with the entry in its current state.', - }), - confirmButton: i18n.translate( - 'xpack.securitySolution.artifacts.confirmWarningModal.confirmButtonText', - { - defaultMessage: 'Add', - } - ), - cancelButton: i18n.translate( - 'xpack.securitySolution.trustedapps.confirmWarningModal.cancelButtonText', - { - defaultMessage: 'Cancel', - } - ), +export const CONFIRM_WARNING_MODAL_LABELS = (entryType: string) => { + return { + title: i18n.translate('xpack.securitySolution.artifacts.confirmWarningModal.title', { + defaultMessage: `Confirm {type}`, + values: { type: entryType }, + }), + body: i18n.translate('xpack.securitySolution.artifacts.confirmWarningModal.body', { + defaultMessage: + 'Using a "*" or a "?" in the value with the "IS" operator can make the entry ineffective. Change the operator to ‘matches’ to ensure wildcards run properly. Select “cancel” to revise your entry, or "add" to continue with the entry in its current state.', + }), + confirmButton: i18n.translate( + 'xpack.securitySolution.artifacts.confirmWarningModal.confirmButtonText', + { + defaultMessage: 'Add', + } + ), + cancelButton: i18n.translate( + 'xpack.securitySolution.trustedapps.confirmWarningModal.cancelButtonText', + { + defaultMessage: 'Cancel', + } + ), + }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index d9db594c4e73c..e6d42f65e7a08 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -19,6 +19,7 @@ import { EuiHorizontalRule, EuiTextArea, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; @@ -196,7 +197,11 @@ export const EventFiltersForm: React.FC( item: updatedFormValues, isValid: updatedValidationResult.isValid, confirmModalLabels: updatedValidationResult.extraWarning - ? CONFIRM_WARNING_MODAL_LABELS + ? CONFIRM_WARNING_MODAL_LABELS( + i18n.translate('xpack.securitySolution.trustedApps.flyoutForm.confirmModal.name', { + defaultMessage: 'trusted application', + }) + ) : undefined, }); }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx index 16ab4f04b68d4..afdb79365ff5c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/field_renderers/field_renderers.tsx @@ -201,6 +201,7 @@ interface DefaultFieldRendererProps { render?: (item: string) => React.ReactNode; rowItems: string[] | null | undefined; sourcererScopeId?: SourcererScopeName; + scopeId?: string; } export const DefaultFieldRendererComponent: React.FC = ({ @@ -212,6 +213,7 @@ export const DefaultFieldRendererComponent: React.FC render, rowItems, sourcererScopeId, + scopeId, }) => { if (rowItems != null && rowItems.length > 0) { const draggables = rowItems.slice(0, displayCount).map((rowItem, index) => { @@ -234,6 +236,7 @@ export const DefaultFieldRendererComponent: React.FC value={rowItem} isAggregatable={true} fieldType={'keyword'} + scopeId={scopeId} > {render ? render(rowItem) : rowItem} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.test.tsx index 1de0eb646eda4..4514409184ba5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.test.tsx @@ -312,7 +312,7 @@ describe('HostName', () => { params: { hostName: props.value, contextID: props.contextId, - scopeId: TableId.alertsOnAlertsPage, + scopeId: 'timeline-1', isDraggable: false, }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx index 705dbcbb0a17c..69e0b34b4b3d3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx @@ -9,7 +9,6 @@ import React, { useCallback, useContext, useMemo } from 'react'; import type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { isString } from 'lodash/fp'; -import { TableId } from '@kbn/securitysolution-data-table'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { HostPanelKey } from '../../../../../flyout/entity_details/host_right'; @@ -81,7 +80,7 @@ const HostNameComponent: React.FC = ({ params: { hostName, contextID: contextId, - scopeId: TableId.alertsOnAlertsPage, + scopeId: timelineID, isDraggable, }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.test.tsx index 6e0edb987f8f2..426839056e51a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.test.tsx @@ -286,7 +286,7 @@ describe('UserName', () => { params: { userName: props.value, contextID: props.contextId, - scopeId: TableId.alertsOnAlertsPage, + scopeId: 'timeline-1', isDraggable: false, }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx index d3f0894346cf8..39069fc7320bb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/user_name.tsx @@ -9,7 +9,6 @@ import React, { useCallback, useContext, useMemo } from 'react'; import type { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { isString } from 'lodash/fp'; -import { TableId } from '@kbn/securitysolution-data-table'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { UserPanelKey } from '../../../../../flyout/entity_details/user_right'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; @@ -79,7 +78,7 @@ const UserNameComponent: React.FC = ({ params: { userName, contextID: contextId, - scopeId: TableId.alertsOnAlertsPage, + scopeId: timelineID, isDraggable, }, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts index a1988cb7a13ae..332b5778d67f7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts @@ -89,7 +89,7 @@ describe('Endpoint artifact packager task', () => { endpointAppContext: mockContext, taskManager: mockTaskManager, }); - manifestTaskInstance.start({ taskManager: taskManagerMock.createStart() }); + void manifestTaskInstance.start({ taskManager: taskManagerMock.createStart() }); mockContext.service.getManifestManager = jest.fn().mockReturnValue(manifestManager); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts index a9c81fbd69e2c..8d77ddecbcf56 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts @@ -40,7 +40,7 @@ export class CompleteExternalResponseActionsTask { private wasStarted = false; private log: Logger; private esClient: ElasticsearchClient | undefined = undefined; - private cleanup: (() => void | Promise) | undefined; + private cleanup: (() => void) | undefined; private taskTimeout = '20m'; // Default. Real value comes from server config private taskInterval = '60s'; // Default. Real value comes from server config @@ -54,7 +54,7 @@ export class CompleteExternalResponseActionsTask { return `${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}-${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION}`; } - public async setup({ taskManager }: CompleteExternalResponseActionsTaskSetupOptions) { + public setup({ taskManager }: CompleteExternalResponseActionsTaskSetupOptions) { if (this.wasSetup) { throw new Error(`Task has already been setup!`); } @@ -143,7 +143,7 @@ export class CompleteExternalResponseActionsTask { this.log.info( `Un-registering task definition [${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}] (if it exists)` ); - taskManager.removeIfExists(COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE); + taskManager.removeIfExists(COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE).catch(() => {}); this.cleanup = undefined; }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts index 14d93a292a573..9497f24c8fc42 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts @@ -140,13 +140,13 @@ describe('CompleteExternalTaskRunner class', () => { return clientMock; } ); - runnerInstance.run(); + void runnerInstance.run(); await waitFor(() => { expect(endpointContextServicesMock.getInternalResponseActionsClient).toHaveBeenCalled(); }); - runnerInstance.cancel(); + await runnerInstance.cancel(); expect(processPendingActionsAbortSignal!.aborted).toBe(true); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts index 01d3ad006405f..13145db5e3e8e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver.ts @@ -17,19 +17,28 @@ import { handleTree } from './resolver/tree/handler'; import { handleEntities } from './resolver/entity/handler'; import { handleEvents } from './resolver/events'; -export const registerResolverRoutes = async ( +export const registerResolverRoutes = ( router: SecuritySolutionPluginRouter, startServices: StartServicesAccessor, config: ConfigType ) => { - const [, { ruleRegistry, licensing }] = await startServices(); + const getRuleRegistry = async () => { + const [, { ruleRegistry }] = await startServices(); + return ruleRegistry; + }; + + const getLicensing = async () => { + const [, { licensing }] = await startServices(); + return licensing; + }; + router.post( { path: '/api/endpoint/resolver/tree', validate: validateTree, options: { authRequired: true }, }, - handleTree(ruleRegistry, config, licensing) + handleTree(getRuleRegistry, config, getLicensing) ); router.post( @@ -38,7 +47,7 @@ export const registerResolverRoutes = async ( validate: validateEvents, options: { authRequired: true }, }, - handleEvents(ruleRegistry) + handleEvents(getRuleRegistry) ); /** diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts index ddc961e369168..7d6e4865c9160 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/events.ts @@ -32,7 +32,7 @@ function createEvents( * requested. */ export function handleEvents( - ruleRegistry: RuleRegistryPluginStartContract + getRuleRegistry: () => Promise ): RequestHandler< unknown, TypeOf, @@ -44,6 +44,7 @@ export function handleEvents( body, } = req; const eventsClient = (await context.core).elasticsearch.client; + const ruleRegistry = await getRuleRegistry(); const alertsClient = await ruleRegistry.getRacClientWithRequest(req); const shouldExcludeColdAndFrozenTiers = await ( await context.core diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts index 48c99cd8ed112..3a3fb1d17816a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/handler.ts @@ -19,15 +19,16 @@ import { featureUsageService } from '../../../services/feature_usage'; import { Fetcher } from './utils/fetch'; export function handleTree( - ruleRegistry: RuleRegistryPluginStartContract, + getRuleRegistry: () => Promise, config: ConfigType, - licensing: LicensingPluginStart + getLicensing: () => Promise ): RequestHandler> { return async (context, req, res) => { const client = (await context.core).elasticsearch.client; const { experimentalFeatures: { insightsRelatedAlertsByProcessAncestry }, } = config; + const licensing = await getLicensing(); const license = await firstValueFrom(licensing.license$); const hasAccessToInsightsRelatedByProcessAncestry = insightsRelatedAlertsByProcessAncestry && license.hasAtLeast('platinum'); @@ -40,7 +41,7 @@ export function handleTree( } const alertsClient = hasAccessToInsightsRelatedByProcessAncestry - ? await ruleRegistry.getRacClientWithRequest(req) + ? await (await getRuleRegistry()).getRacClientWithRequest(req) : undefined; const fetcher = new Fetcher(client, alertsClient); const body = await fetcher.tree({ ...req.body, shouldExcludeColdAndFrozenTiers }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/unified_manifest_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/unified_manifest_client.ts index ae1acdbd8e4cf..db47c43b68607 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/unified_manifest_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/unified_manifest_client.ts @@ -98,11 +98,7 @@ export class UnifiedManifestClient { const unifiedManifestsFetcher = this.fetchAllUnifiedManifests(this.savedObjectsClient, options); for await (const unifiedManifests of unifiedManifestsFetcher) { - if (cb.constructor.name === 'AsyncFunction') { - await cb(unifiedManifests); - } else { - cb(unifiedManifests); - } + await cb(unifiedManifests); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts index 05bd638956f89..202683c8d4c97 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts @@ -135,7 +135,7 @@ export class QueueProcessor { public addToQueue(...data: T[]) { if (data.length > 0) { this.queue.push(...data); - this.processQueue(); + this.processQueue().catch(() => {}); } } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 34a3b44b551d6..7dddfa32a1967 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -572,7 +572,7 @@ describe('ingest_integration tests ', () => { const validDateYesterday = moment.utc().subtract(1, 'day'); - it('should throw if endpointProtectionUpdates productFeature is disabled and user modifies global_manifest_version', () => { + it('should throw if endpointProtectionUpdates productFeature is disabled and user modifies global_manifest_version', async () => { productFeaturesService = createProductFeaturesServiceMock( ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates') ); @@ -587,7 +587,7 @@ describe('ingest_integration tests ', () => { ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value.global_manifest_version = '2023-01-01'; - expect(() => + await expect(() => callback(policyConfig, soClient, esClient, requestContextMock.convertContext(ctx), req) ).rejects.toThrow( 'To modify protection updates, you must add at least Endpoint Complete to your project.' diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index 99035d3b66a88..4e5a1e2f44ff2 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -223,7 +223,7 @@ export const getPackagePolicyUpdateCallback = ( validateEndpointPackagePolicy(endpointIntegrationData.inputs); - notifyProtectionFeatureUsage( + await notifyProtectionFeatureUsage( endpointIntegrationData, featureUsageService, endpointMetadataService @@ -294,7 +294,9 @@ export const getPackagePolicyPostCreateCallback = ( exceptionsClient, integrationConfig.value.eventFilters, packagePolicy - ); + ).catch((error) => { + logger.error(`Failed to create event filters: ${error}`); + }); } return packagePolicy; }; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts index d9bab942de2ae..5e5cefd5d2a90 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts @@ -55,19 +55,19 @@ export const createEventFilters = async ( } } - createNonInteractiveSessionEventFilter(logger, exceptionsClient, packagePolicy); + await createNonInteractiveSessionEventFilter(logger, exceptionsClient, packagePolicy); }; /** * Create an Event Filter for non-interactive sessions and attach it to the policy */ -export const createNonInteractiveSessionEventFilter = ( +export const createNonInteractiveSessionEventFilter = async ( logger: Logger, exceptionsClient: ExceptionListClient, packagePolicy: PackagePolicy -): void => { +): Promise => { try { - exceptionsClient.createExceptionListItem({ + await exceptionsClient.createExceptionListItem({ listId: ENDPOINT_EVENT_FILTERS_LIST_ID, description: i18n.translate( 'xpack.securitySolution.fleetIntegration.elasticDefend.eventFilter.nonInteractiveSessions.description', diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts index 9106eba06e780..13ddda419ff90 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_protection_updates_note.ts @@ -23,11 +23,8 @@ export const removeProtectionUpdatesNote = async ( id: policy.id, }, }); - await pMap( - foundProtectionUpdatesNotes.saved_objects, - (protectionUpdatesNote: { id: string }) => { - soClient.delete(protectionUpdatesNoteSavedObjectType, protectionUpdatesNote.id); - } + await pMap(foundProtectionUpdatesNotes.saved_objects, (protectionUpdatesNote: { id: string }) => + soClient.delete(protectionUpdatesNoteSavedObjectType, protectionUpdatesNote.id) ); } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts index 61bea17db1156..2a8705d03b877 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts @@ -19,8 +19,8 @@ describe('legacy_create_rule_actions_saved_object', () => { savedObjectsClient = savedObjectsClientMock.create(); }); - test('it creates a rule actions saved object with empty actions array', () => { - legacyCreateRuleActionsSavedObject({ + test('it creates a rule actions saved object with empty actions array', async () => { + await legacyCreateRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, actions: [], @@ -43,8 +43,8 @@ describe('legacy_create_rule_actions_saved_object', () => { }); }); - test('it creates a rule actions saved object with 1 single action', () => { - legacyCreateRuleActionsSavedObject({ + test('it creates a rule actions saved object with 1 single action', async () => { + await legacyCreateRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, actions: [ @@ -90,8 +90,8 @@ describe('legacy_create_rule_actions_saved_object', () => { }); }); - test('it creates a rule actions saved object with 2 actions', () => { - legacyCreateRuleActionsSavedObject({ + test('it creates a rule actions saved object with 2 actions', async () => { + await legacyCreateRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, actions: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts index 1f598f0ccf78b..ae3e21881d7af 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts @@ -33,8 +33,8 @@ describe('legacy_get_rule_actions_saved_object', () => { }); }); - test('calls "savedObjectsClient.find" with the expected "hasReferences"', () => { - legacyGetRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, logger }); + test('calls "savedObjectsClient.find" with the expected "hasReferences"', async () => { + await legacyGetRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, logger }); const [[arg1]] = savedObjectsClient.find.mock.calls; expect(arg1).toEqual({ hasReference: { id: '123', type: 'alert' }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 7a3fd49dcd2d1..5cbaa44834302 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -69,7 +69,7 @@ import { wrapSearchSourceClient } from './wrap_search_source_client'; const PREVIEW_TIMEOUT_SECONDS = 60; const MAX_ROUTE_CONCURRENCY = 10; -export const previewRulesRoute = async ( +export const previewRulesRoute = ( router: SecuritySolutionPluginRouter, config: ConfigType, ml: SetupPlugins['ml'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts index 67457b1574000..6fac2725754d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts @@ -96,7 +96,7 @@ describe('ScheduleNotificationResponseActions', () => { }, }, ]; - scheduleNotificationResponseActions({ signals, responseActions }); + await scheduleNotificationResponseActions({ signals, responseActions }); expect(osqueryActionMock.create).toHaveBeenCalledWith({ ...defaultQueryResultParams, @@ -123,7 +123,7 @@ describe('ScheduleNotificationResponseActions', () => { }, }, ]; - scheduleNotificationResponseActions({ signals, responseActions }); + await scheduleNotificationResponseActions({ signals, responseActions }); expect(osqueryActionMock.create).toHaveBeenCalledWith({ ...defaultPackResultParams, @@ -149,7 +149,7 @@ describe('ScheduleNotificationResponseActions', () => { }, }, ]; - scheduleNotificationResponseActions({ signals, responseActions }); + await scheduleNotificationResponseActions({ signals, responseActions }); expect(endpointActionMock.getInternalResponseActionsClient).toHaveBeenCalledTimes(1); expect(endpointActionMock.getInternalResponseActionsClient).toHaveBeenCalledWith({ @@ -188,7 +188,7 @@ describe('ScheduleNotificationResponseActions', () => { }, }, ]; - scheduleNotificationResponseActions({ + await scheduleNotificationResponseActions({ signals, responseActions, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts index 93c8e22ac2316..d20995291b5f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { each } from 'lodash'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { SetupPlugins } from '../../../plugin_contract'; import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; @@ -24,22 +23,24 @@ export const getScheduleNotificationResponseActionsService = osqueryCreateActionService, endpointAppContextService, }: ScheduleNotificationResponseActionsService) => - ({ signals, responseActions }: ScheduleNotificationActions) => { + async ({ signals, responseActions }: ScheduleNotificationActions) => { const alerts = (signals as Alert[]).filter((alert) => alert.agent?.id) as AlertWithAgent[]; - each(responseActions, (responseAction) => { - if ( - responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] && - osqueryCreateActionService - ) { - osqueryResponseAction(responseAction, osqueryCreateActionService, { - alerts, - }); - } - if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { - endpointResponseAction(responseAction, endpointAppContextService, { - alerts, - }); - } - }); + await Promise.all( + responseActions.map(async (responseAction) => { + if ( + responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] && + osqueryCreateActionService + ) { + await osqueryResponseAction(responseAction, osqueryCreateActionService, { + alerts, + }); + } + if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { + await endpointResponseAction(responseAction, endpointAppContextService, { + alerts, + }); + } + }) + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts index b77ea7742077d..9cbb6ee9ce2e5 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts @@ -81,7 +81,7 @@ describe('AssetCriticalityDataClient', () => { }); it('searches in the asset criticality index', async () => { - subject.search({ query: { match_all: {} } }); + await subject.search({ query: { match_all: {} } }); expect(esClientMock.search).toHaveBeenCalledWith( expect.objectContaining({ index: '.asset-criticality.asset-criticality-default' }) @@ -89,7 +89,7 @@ describe('AssetCriticalityDataClient', () => { }); it('requires a query parameter', async () => { - subject.search({ query: { match_all: {} } }); + await subject.search({ query: { match_all: {} } }); expect(esClientMock.search).toHaveBeenCalledWith( expect.objectContaining({ body: { query: { match_all: {} } } }) @@ -97,13 +97,13 @@ describe('AssetCriticalityDataClient', () => { }); it('accepts a size parameter', async () => { - subject.search({ query: { match_all: {} }, size: 100 }); + await subject.search({ query: { match_all: {} }, size: 100 }); expect(esClientMock.search).toHaveBeenCalledWith(expect.objectContaining({ size: 100 })); }); it('defaults to the default query size', async () => { - subject.search({ query: { match_all: {} } }); + await subject.search({ query: { match_all: {} } }); const defaultSize = 1_000; expect(esClientMock.search).toHaveBeenCalledWith( @@ -112,14 +112,14 @@ describe('AssetCriticalityDataClient', () => { }); it('caps the size to the maximum query size', async () => { - subject.search({ query: { match_all: {} }, size: 999999 }); + await subject.search({ query: { match_all: {} }, size: 999999 }); const maxSize = 100_000; expect(esClientMock.search).toHaveBeenCalledWith(expect.objectContaining({ size: maxSize })); }); it('ignores an index_not_found_exception if the criticality index does not exist', async () => { - subject.search({ query: { match_all: {} } }); + await subject.search({ query: { match_all: {} } }); expect(esClientMock.search).toHaveBeenCalledWith( expect.objectContaining({ ignore_unavailable: true }) diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts index f936ca6a83c76..b3d25855ba427 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts @@ -310,7 +310,7 @@ export const runTask = async ({ }; telemetry.reportEvent(RISK_SCORE_EXECUTION_SUCCESS_EVENT.eventType, telemetryEvent); - riskScoreService.scheduleLatestTransformNow(); + await riskScoreService.scheduleLatestTransformNow(); if (isCancelled()) { log('task was cancelled'); diff --git a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts index 3597a109adbfe..b59dafb2f0eb7 100644 --- a/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/product_features_service/product_features_service.test.ts @@ -192,7 +192,7 @@ describe('ProductFeaturesService', () => { expect(mockHttpSetup.registerOnPostAuth).toHaveBeenCalledTimes(1); }); - it('should authorize when no tag matches', () => { + it('should authorize when no tag matches', async () => { const experimentalFeatures = {} as ExperimentalFeatures; const productFeaturesService = new ProductFeaturesService( loggerMock.create(), @@ -200,14 +200,14 @@ describe('ProductFeaturesService', () => { ); productFeaturesService.registerApiAccessControl(mockHttpSetup); - lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit); + await lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit); expect(MockedProductFeatures.mock.instances[0].isActionRegistered).not.toHaveBeenCalled(); expect(res.notFound).not.toHaveBeenCalled(); expect(toolkit.next).toHaveBeenCalledTimes(1); }); - it('should check when tag matches and return not found when not action registered', () => { + it('should check when tag matches and return not found when not action registered', async () => { const experimentalFeatures = {} as ExperimentalFeatures; const productFeaturesService = new ProductFeaturesService( loggerMock.create(), @@ -218,7 +218,7 @@ describe('ProductFeaturesService', () => { (MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce( false ); - lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit); + await lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit); expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith( 'api:securitySolution-foo' @@ -227,7 +227,7 @@ describe('ProductFeaturesService', () => { expect(toolkit.next).not.toHaveBeenCalled(); }); - it('should check when tag matches and continue when action registered', () => { + it('should check when tag matches and continue when action registered', async () => { const experimentalFeatures = {} as ExperimentalFeatures; const productFeaturesService = new ProductFeaturesService( loggerMock.create(), @@ -238,7 +238,7 @@ describe('ProductFeaturesService', () => { (MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce( true ); - lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit); + await lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit); expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith( 'api:securitySolution-foo' @@ -247,7 +247,7 @@ describe('ProductFeaturesService', () => { expect(toolkit.next).toHaveBeenCalledTimes(1); }); - it('should check when productFeature tag when it matches and return not found when not enabled', () => { + it('should check when productFeature tag when it matches and return not found when not enabled', async () => { const experimentalFeatures = {} as ExperimentalFeatures; const productFeaturesService = new ProductFeaturesService( loggerMock.create(), @@ -257,14 +257,14 @@ describe('ProductFeaturesService', () => { productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(false); - lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit); + await lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit); expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo'); expect(res.notFound).toHaveBeenCalledTimes(1); expect(toolkit.next).not.toHaveBeenCalled(); }); - it('should check when productFeature tag when it matches and continue when enabled', () => { + it('should check when productFeature tag when it matches and continue when enabled', async () => { const experimentalFeatures = {} as ExperimentalFeatures; const productFeaturesService = new ProductFeaturesService( loggerMock.create(), @@ -274,7 +274,7 @@ describe('ProductFeaturesService', () => { productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(true); - lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit); + await lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit); expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo'); expect(res.notFound).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index eff642239da3a..5b59282a320ea 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -596,7 +596,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { yield alerts; } - this.closePointInTime(pitId); + await this.closePointInTime(pitId); } public async fetchPolicyConfigs(id: string) { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index a788a59b63b71..0f21cb58824f6 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -18,6 +18,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { exhaustMap, Subject, takeUntil, timer } from 'rxjs'; import type { ITelemetryReceiver } from './receiver'; import { copyAllowlistedFields, filterList } from './filterlists'; import { createTelemetryTaskConfigs } from './tasks'; @@ -92,10 +93,10 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { private readonly initialCheckDelayMs = 10 * 1000; private readonly checkIntervalMs = 60 * 1000; private readonly logger: Logger; + private readonly stop$ = new Subject(); private maxQueueSize = telemetryConfiguration.telemetry_max_buffer_size; private telemetryStart?: TelemetryPluginStart; private telemetrySetup?: TelemetryPluginSetup; - private intervalId?: NodeJS.Timeout; private isSending = false; private receiver: ITelemetryReceiver | undefined; private queue: TelemetryEvent[] = []; @@ -162,16 +163,16 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { } tlog(this.logger, `Starting local task`); - setTimeout(() => { - this.sendIfDue(); - this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); - }, this.initialCheckDelayMs); + timer(this.initialCheckDelayMs, this.checkIntervalMs) + .pipe( + takeUntil(this.stop$), + exhaustMap(() => this.sendIfDue()) + ) + .subscribe(); } public stop() { - if (this.intervalId) { - clearInterval(this.intervalId); - } + this.stop$.next(); } public queueTelemetryEvents(events: TelemetryEvent[]) { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts index 784c34bf11d7d..866bd7cd1ba0c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task_metrics.test.ts @@ -35,7 +35,7 @@ describe('task metrics', () => { }); it('should record passed task metrics', async () => { - const metric = sendMetric('test'); + const metric = await sendMetric('test'); expect(metric.name).toEqual('test'); expect(metric.passed).toBeTruthy(); @@ -49,7 +49,7 @@ describe('task metrics', () => { jest.spyOn(telemetryConfiguration, 'use_async_sender', 'get').mockReturnValue(false); const trace = taskMetricsService.start('test'); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(0); expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(0); @@ -57,7 +57,7 @@ describe('task metrics', () => { }); it('should record failed task metrics', async () => { - const metric = sendMetric('test', Error('Boom!')); + const metric = await sendMetric('test', Error('Boom!')); expect(metric.name).toEqual('test'); expect(metric.passed).toBeFalsy(); @@ -67,9 +67,9 @@ describe('task metrics', () => { expect(metric.end_time).toBeGreaterThan(0); }); - function sendMetric(name: string, error?: Error): TaskMetric { + async function sendMetric(name: string, error?: Error): Promise { const trace = taskMetricsService.start(name); - taskMetricsService.end(trace, error); + await taskMetricsService.end(trace, error); expect(mockTelemetryEventsSender.sendAsync).toHaveBeenCalledTimes(1); const events = mockTelemetryEventsSender.sendAsync.mock.calls[0][1]; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts index 701f42a89f4c0..0c95ccf651013 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/configuration.ts @@ -45,7 +45,7 @@ export function createTelemetryConfigurationTaskConfig() { if (manifest.notModified) { log.l('No new configuration artifact found, skipping...'); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } @@ -106,14 +106,14 @@ export function createTelemetryConfigurationTaskConfig() { _receiver.setNumDocsToSample(configArtifact.pagination_config.num_docs_to_sample); } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); log.l(`Updated TelemetryConfiguration: ${JSON.stringify(telemetryConfiguration)}`); return 0; } catch (err) { log.l(`Failed to set telemetry configuration due to ${err.message}`); telemetryConfiguration.resetAllToDefault(); - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts index f7616830716f1..91a2bf2a5b85a 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts @@ -62,7 +62,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n if (!prebuiltRules) { log.l('no prebuilt rules found'); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } @@ -118,7 +118,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); log.l( `Task: ${taskId} executed. Processed ${detectionRuleExceptionsJson.length} exceptions` @@ -126,7 +126,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n return detectionRuleExceptionsJson.length; } catch (err) { - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts index 85ac4d744feae..1c691aea4155f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts @@ -58,7 +58,7 @@ export function createTelemetryDiagnosticsTaskConfig() { if (alerts.length === 0) { log.l('no diagnostic alerts retrieved'); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return alertCount; } @@ -67,10 +67,10 @@ export function createTelemetryDiagnosticsTaskConfig() { await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_ALERTS, processedAlerts); } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return alertCount; } catch (err) { - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index a55cbb4eda19a..aceb3ebf98926 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -94,14 +94,14 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { await sender.sendOnDemand(TELEMETRY_CHANNEL_ENDPOINT_META, batch); } } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return documents.length; } catch (err) { log.l(`Error running endpoint alert telemetry task`, { error: JSON.stringify(err), }); - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts index 5a869ecade992..69a3ee646f0cf 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/filterlists.ts @@ -44,7 +44,7 @@ export function createTelemetryFilterListArtifactTaskConfig() { const manifest = await artifactService.getArtifact(artifactName); if (manifest.notModified) { log.l('No new filterlist artifact found, skipping...'); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } @@ -53,12 +53,12 @@ export function createTelemetryFilterListArtifactTaskConfig() { filterList.endpointAlerts = artifact.endpoint_alerts; filterList.exceptionLists = artifact.exception_lists; filterList.prebuiltRulesAlerts = artifact.prebuilt_rules_alerts; - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } catch (err) { log.l(`Failed to set telemetry filterlist artifact due to ${err.message}`); filterList.resetAllToDefault(); - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts index 921ff422bded5..3fda2878d4566 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts @@ -62,7 +62,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n if (index === undefined) { log.l(`alerts index is not ready yet, skipping telemetry task`); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } @@ -71,7 +71,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n taskExecutionPeriod.current )) { if (alerts.length === 0) { - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } @@ -100,17 +100,17 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n const batches = batchTelemetryRecords(enrichedAlerts, maxTelemetryBatch); const promises = batches.map(async (batch) => { - sender.sendOnDemand(TELEMETRY_CHANNEL_DETECTION_ALERTS, batch); + await sender.sendOnDemand(TELEMETRY_CHANNEL_DETECTION_ALERTS, batch); }); await Promise.all(promises); } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return 0; } catch (err) { logger.error('could not complete prebuilt alerts telemetry task'); - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index cc51fa7ba6d5b..09e1d14558c54 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -158,10 +158,10 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) if (valueListMetaData?.total_list_count) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); } - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return trustedApplicationsCount + endpointExceptionsCount + endpointEventFiltersCount; } catch (err) { - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts index fa0a280fb7026..c016f029fa2a5 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -67,7 +67,7 @@ export function createTelemetryTimelineTaskConfig() { }); if (result.timeline) { - sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); + await sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); counter += 1; } else { log.l('no events in timeline'); @@ -76,11 +76,11 @@ export function createTelemetryTimelineTaskConfig() { log.l(`sent ${counter} timelines. Concluding timeline task.`); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return counter; } catch (err) { - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts index 9207adf7ae748..31718ec47b1e6 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines_diagnostic.ts @@ -67,7 +67,7 @@ export function createTelemetryDiagnosticTimelineTaskConfig() { }); if (result.timeline) { - sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); + await sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [result.timeline]); counter += 1; } else { log.l('no events in timeline'); @@ -76,11 +76,11 @@ export function createTelemetryDiagnosticTimelineTaskConfig() { log.l(`sent ${counter} timelines. Concluding timeline task.`); - taskMetricsService.end(trace); + await taskMetricsService.end(trace); return counter; } catch (err) { - taskMetricsService.end(trace, err); + await taskMetricsService.end(trace, err); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts index 3a55b4fef8086..0e288e0099596 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts @@ -17,7 +17,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; -export const copyTimelineRoute = async ( +export const copyTimelineRoute = ( router: SecuritySolutionPluginRouter, _: ConfigType, security: SetupPlugins['security'] diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 998e02aba56ad..b0eb25bd3c18f 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -409,74 +409,80 @@ export class Plugin implements ISecuritySolutionPlugin { this.completeExternalResponseActionsTask.setup({ taskManager: plugins.taskManager }); } - core.getStartServices().then(([_, depsStart]) => { - appClientFactory.setup({ - getSpaceId: depsStart.spaces?.spacesService?.getSpaceId, - config, - kibanaVersion: pluginContext.env.packageInfo.version, - kibanaBranch: pluginContext.env.packageInfo.branch, - }); + core + .getStartServices() + .then(async ([_, depsStart]) => { + appClientFactory.setup({ + getSpaceId: depsStart.spaces?.spacesService?.getSpaceId, + config, + kibanaVersion: pluginContext.env.packageInfo.version, + kibanaBranch: pluginContext.env.packageInfo.branch, + }); + + const endpointFieldsStrategy = endpointFieldsProvider( + this.endpointAppContextService, + depsStart.data.indexPatterns + ); + plugins.data.search.registerSearchStrategy( + ENDPOINT_FIELDS_SEARCH_STRATEGY, + endpointFieldsStrategy + ); - const endpointFieldsStrategy = endpointFieldsProvider( - this.endpointAppContextService, - depsStart.data.indexPatterns - ); - plugins.data.search.registerSearchStrategy( - ENDPOINT_FIELDS_SEARCH_STRATEGY, - endpointFieldsStrategy - ); + const endpointPackagePoliciesStatsStrategy = + endpointPackagePoliciesStatsSearchStrategyProvider(this.endpointAppContextService); + plugins.data.search.registerSearchStrategy( + ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY, + endpointPackagePoliciesStatsStrategy + ); - const endpointPackagePoliciesStatsStrategy = - endpointPackagePoliciesStatsSearchStrategyProvider(this.endpointAppContextService); - plugins.data.search.registerSearchStrategy( - ENDPOINT_PACKAGE_POLICIES_STATS_STRATEGY, - endpointPackagePoliciesStatsStrategy - ); + const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( + depsStart.data, + this.endpointContext, + depsStart.spaces?.spacesService?.getSpaceId, + ruleDataClient + ); - const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( - depsStart.data, - this.endpointContext, - depsStart.spaces?.spacesService?.getSpaceId, - ruleDataClient - ); + plugins.data.search.registerSearchStrategy( + 'securitySolutionSearchStrategy', + securitySolutionSearchStrategy + ); + const endpointSearchStrategy = endpointSearchStrategyProvider( + depsStart.data, + this.endpointContext + ); - plugins.data.search.registerSearchStrategy( - 'securitySolutionSearchStrategy', - securitySolutionSearchStrategy - ); - const endpointSearchStrategy = endpointSearchStrategyProvider( - depsStart.data, - this.endpointContext - ); + plugins.data.search.registerSearchStrategy( + ENDPOINT_SEARCH_STRATEGY, + endpointSearchStrategy + ); - plugins.data.search.registerSearchStrategy(ENDPOINT_SEARCH_STRATEGY, endpointSearchStrategy); - - /** - * Register a config for the security guide - */ - if (depsStart.cloudExperiments && i18n.getLocale() === 'en') { - try { - depsStart.cloudExperiments - .getVariation('security-solutions.guided-onboarding-content', defaultGuideTranslations) - .then((variation) => { - plugins.guidedOnboarding?.registerGuideConfig( - siemGuideId, - getSiemGuideConfig(variation) - ); - }); - } catch { + /** + * Register a config for the security guide + */ + if (depsStart.cloudExperiments && i18n.getLocale() === 'en') { + try { + const variation = await depsStart.cloudExperiments.getVariation( + 'security-solutions.guided-onboarding-content', + defaultGuideTranslations + ); + plugins.guidedOnboarding?.registerGuideConfig( + siemGuideId, + getSiemGuideConfig(variation) + ); + } catch { + plugins.guidedOnboarding?.registerGuideConfig( + siemGuideId, + getSiemGuideConfig(defaultGuideTranslations) + ); + } + } else { plugins.guidedOnboarding?.registerGuideConfig( siemGuideId, getSiemGuideConfig(defaultGuideTranslations) ); } - } else { - plugins.guidedOnboarding?.registerGuideConfig( - siemGuideId, - getSiemGuideConfig(defaultGuideTranslations) - ); - } - }); + }) + .catch(() => {}); // it shouldn't reject, but just in case setIsElasticCloudDeployment(plugins.cloud.isCloudEnabled ?? false); @@ -586,29 +592,32 @@ export class Plugin implements ISecuritySolutionPlugin { }); // Migrate artifacts to fleet and then start the manifest task after that is done - plugins.fleet.fleetSetupCompleted().then(() => { - if (this.manifestTask) { - logger.info('Dependent plugin setup complete - Starting ManifestTask'); - this.manifestTask.start({ - taskManager, - }); - } else { - logger.error(new Error('User artifacts task not available.')); - } + plugins.fleet + .fleetSetupCompleted() + .then(async () => { + if (this.manifestTask) { + logger.info('Dependent plugin setup complete - Starting ManifestTask'); + await this.manifestTask.start({ + taskManager, + }); + } else { + logger.error(new Error('User artifacts task not available.')); + } - turnOffPolicyProtectionsIfNotSupported( - core.elasticsearch.client.asInternalUser, - endpointFleetServicesFactory.asInternalUser(), - productFeaturesService, - logger - ); + await turnOffPolicyProtectionsIfNotSupported( + core.elasticsearch.client.asInternalUser, + endpointFleetServicesFactory.asInternalUser(), + productFeaturesService, + logger + ); - turnOffAgentPolicyFeatures( - endpointFleetServicesFactory.asInternalUser(), - productFeaturesService, - logger - ); - }); + await turnOffAgentPolicyFeatures( + endpointFleetServicesFactory.asInternalUser(), + productFeaturesService, + logger + ); + }) + .catch(() => {}); // License related start licenseService.start(this.licensing$); @@ -653,22 +662,26 @@ export class Plugin implements ISecuritySolutionPlugin { }); if (plugins.taskManager) { - this.completeExternalResponseActionsTask.start({ - taskManager: plugins.taskManager, - esClient: core.elasticsearch.client.asInternalUser, - }); + this.completeExternalResponseActionsTask + .start({ + taskManager: plugins.taskManager, + esClient: core.elasticsearch.client.asInternalUser, + }) + .catch(() => {}); // it shouldn't refuse, but just in case } - this.telemetryReceiver.start( - core, - (type: string) => core.savedObjects.getIndexForType(type), - DEFAULT_ALERTS_INDEX, - this.endpointAppContextService, - exceptionListClient, - packageService - ); + this.telemetryReceiver + .start( + core, + (type: string) => core.savedObjects.getIndexForType(type), + DEFAULT_ALERTS_INDEX, + this.endpointAppContextService, + exceptionListClient, + packageService + ) + .catch(() => {}); - artifactService.start(this.telemetryReceiver); + artifactService.start(this.telemetryReceiver).catch(() => {}); this.asyncTelemetryEventsSender.start(plugins.telemetry); @@ -681,8 +694,8 @@ export class Plugin implements ISecuritySolutionPlugin { const endpointPkgInstallationPromise = this.endpointContext.service .getInternalFleetServices() .packages.getInstallation(FLEET_ENDPOINT_PACKAGE); - Promise.all([endpointPkgInstallationPromise, plugins.fleet?.fleetSetupCompleted()]).then( - ([endpointPkgInstallation]) => { + Promise.all([endpointPkgInstallationPromise, plugins.fleet?.fleetSetupCompleted()]) + .then(async ([endpointPkgInstallation]) => { if (plugins.taskManager) { if ( endpointPkgInstallation?.version && @@ -691,10 +704,10 @@ export class Plugin implements ISecuritySolutionPlugin { return; } - this.checkMetadataTransformsTask?.start({ taskManager: plugins.taskManager }); + await this.checkMetadataTransformsTask?.start({ taskManager: plugins.taskManager }); } - } - ); + }) + .catch(() => {}); // it shouldn't reject, but just in case if (registerIngestCallback) { registerIngestCallback( @@ -727,11 +740,11 @@ export class Plugin implements ISecuritySolutionPlugin { public stop() { this.logger.debug('Stopping plugin'); - this.asyncTelemetryEventsSender.stop(); + this.asyncTelemetryEventsSender.stop().catch(() => {}); this.telemetryEventsSender.stop(); this.endpointAppContextService.stop(); this.policyWatcher?.stop(); - this.completeExternalResponseActionsTask.stop(); + this.completeExternalResponseActionsTask.stop().catch(() => {}); licenseService.stop(); } } diff --git a/x-pack/plugins/security_solution/server/usage/queries/get_case_comments.ts b/x-pack/plugins/security_solution/server/usage/queries/get_case_comments.ts index 43acb1db00c1b..f74642507de34 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/get_case_comments.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/get_case_comments.ts @@ -50,7 +50,7 @@ export const getCaseComments = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/security_solution/server/usage/queries/get_detection_rules.ts b/x-pack/plugins/security_solution/server/usage/queries/get_detection_rules.ts index 3452a364503ce..ae0cb67e02423 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/get_detection_rules.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/get_detection_rules.ts @@ -74,7 +74,7 @@ export const getDetectionRules = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts b/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts index 9b7140d734d16..c4b795b608466 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts @@ -61,7 +61,7 @@ export const legacyGetRuleActions = async ({ } try { - finder.close(); + await finder.close(); } catch (exception) { // This is just a pre-caution in case the finder does a throw we don't want to blow up // the response. We have seen this within e2e test containers but nothing happen in normal diff --git a/x-pack/plugins/security_solution_serverless/server/plugin.ts b/x-pack/plugins/security_solution_serverless/server/plugin.ts index cc89c3ce76e09..2bf9e901a4a24 100644 --- a/x-pack/plugins/security_solution_serverless/server/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/server/plugin.ts @@ -125,23 +125,27 @@ export class SecuritySolutionServerlessPlugin const internalESClient = coreStart.elasticsearch.client.asInternalUser; const internalSOClient = coreStart.savedObjects.createInternalRepository(); - this.cloudSecurityUsageReportingTask?.start({ - taskManager: pluginsSetup.taskManager, - interval: cloudSecurityMetringTaskProperties.interval, - }); - - this.endpointUsageReportingTask?.start({ - taskManager: pluginsSetup.taskManager, - interval: ENDPOINT_METERING_TASK.INTERVAL, - }); - - this.nlpCleanupTask?.start({ taskManager: pluginsSetup.taskManager }); + this.cloudSecurityUsageReportingTask + ?.start({ + taskManager: pluginsSetup.taskManager, + interval: cloudSecurityMetringTaskProperties.interval, + }) + .catch(() => {}); + + this.endpointUsageReportingTask + ?.start({ + taskManager: pluginsSetup.taskManager, + interval: ENDPOINT_METERING_TASK.INTERVAL, + }) + .catch(() => {}); + + this.nlpCleanupTask?.start({ taskManager: pluginsSetup.taskManager }).catch(() => {}); setEndpointPackagePolicyServerlessFlag( internalSOClient, internalESClient, pluginsSetup.fleet.packagePolicyService - ); + ).catch(() => {}); return {}; } diff --git a/x-pack/plugins/serverless_search/server/lib/indices/fetch_index.test.ts b/x-pack/plugins/serverless_search/server/lib/indices/fetch_index.test.ts index 88edecd4cfd9d..75bc95365bb04 100644 --- a/x-pack/plugins/serverless_search/server/lib/indices/fetch_index.test.ts +++ b/x-pack/plugins/serverless_search/server/lib/indices/fetch_index.test.ts @@ -61,13 +61,13 @@ describe('fetch index lib function', () => { jest.clearAllMocks(); }); - it('should return index if all client calls succeed', () => { + it('should return index if all client calls succeed', async () => { mockClient.indices.get.mockResolvedValue({ ...regularIndexResponse }); mockClient.indices.stats.mockResolvedValue(regularIndexStatsResponse); mockClient.count.mockResolvedValue(indexCountResponse); (fetchConnectorByIndexName as unknown as jest.Mock).mockResolvedValue(indexConnector); - expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ + await expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ index: { aliases: {}, count: 100, @@ -77,7 +77,7 @@ describe('fetch index lib function', () => { }); }); - it('should throw an error if get index rejects', () => { + it('should throw an error if get index rejects', async () => { const expectedError = new Error('Boom!'); mockClient.indices.get.mockRejectedValue(expectedError); @@ -85,10 +85,10 @@ describe('fetch index lib function', () => { mockClient.count.mockResolvedValue(indexCountResponse); (fetchConnectorByIndexName as unknown as jest.Mock).mockResolvedValue(indexConnector); - expect(fetchIndex(client(), indexName)).rejects.toEqual(expectedError); + await expect(fetchIndex(client(), indexName)).rejects.toEqual(expectedError); }); - it('should return partial data if index stats rejects', () => { + it('should return partial data if index stats rejects', async () => { const expectedError = new Error('Boom!'); mockClient.indices.get.mockResolvedValue({ ...regularIndexResponse }); @@ -96,7 +96,7 @@ describe('fetch index lib function', () => { mockClient.count.mockResolvedValue(indexCountResponse); (fetchConnectorByIndexName as unknown as jest.Mock).mockResolvedValue(indexConnector); - expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ + await expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ index: { aliases: {}, count: 100, @@ -105,7 +105,7 @@ describe('fetch index lib function', () => { }); }); - it('should return partial data if index count rejects', () => { + it('should return partial data if index count rejects', async () => { const expectedError = new Error('Boom!'); mockClient.indices.get.mockResolvedValue({ ...regularIndexResponse }); @@ -113,7 +113,7 @@ describe('fetch index lib function', () => { mockClient.count.mockRejectedValue(expectedError); (fetchConnectorByIndexName as unknown as jest.Mock).mockResolvedValue(indexConnector); - expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ + await expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ index: { aliases: {}, count: 0, @@ -123,7 +123,7 @@ describe('fetch index lib function', () => { }); }); - it('should return partial data if fetch connector rejects', () => { + it('should return partial data if fetch connector rejects', async () => { const expectedError = new Error('Boom!'); mockClient.indices.get.mockResolvedValue({ ...regularIndexResponse }); @@ -131,7 +131,7 @@ describe('fetch index lib function', () => { mockClient.count.mockResolvedValue(indexCountResponse); (fetchConnectorByIndexName as unknown as jest.Mock).mockRejectedValue(expectedError); - expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ + await expect(fetchIndex(client(), indexName)).resolves.toMatchObject({ index: { aliases: {}, count: 100, diff --git a/x-pack/plugins/serverless_search/server/plugin.ts b/x-pack/plugins/serverless_search/server/plugin.ts index a7b779a1239c7..0601b099d8a0d 100644 --- a/x-pack/plugins/serverless_search/server/plugin.ts +++ b/x-pack/plugins/serverless_search/server/plugin.ts @@ -34,7 +34,7 @@ export interface RouteDependencies { http: CoreSetup['http']; logger: Logger; router: IRouter; - security: SecurityPluginStart; + getSecurity: () => Promise; } export class ServerlessSearchPlugin @@ -49,7 +49,6 @@ export class ServerlessSearchPlugin // @ts-ignore config is not used for now private readonly config: ServerlessSearchConfig; private readonly logger: Logger; - private security?: SecurityPluginStart; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -82,25 +81,23 @@ export class ServerlessSearchPlugin { serverless, usageCollection }: SetupDependencies ) { const router = http.createRouter(); - getStartServices().then(([, { security }]) => { - this.security = security; - const dependencies = { - http, - logger: this.logger, - router, - security: this.security, - }; + const dependencies = { + http, + logger: this.logger, + router, + getSecurity: async () => { + const [, { security }] = await getStartServices(); + return security; + }, + }; - registerApiKeyRoutes(dependencies); - registerConnectorsRoutes(dependencies); - registerIndicesRoutes(dependencies); - registerMappingRoutes(dependencies); - }); + registerApiKeyRoutes(dependencies); + registerConnectorsRoutes(dependencies); + registerIndicesRoutes(dependencies); + registerMappingRoutes(dependencies); if (usageCollection) { - getStartServices().then(() => { - registerTelemetryUsageCollector(usageCollection, this.logger); - }); + registerTelemetryUsageCollector(usageCollection, this.logger); } serverless.setupProjectSettings(SEARCH_PROJECT_SETTINGS); @@ -108,7 +105,7 @@ export class ServerlessSearchPlugin } public start(core: CoreStart, { dataViews }: StartDependencies) { - this.createDefaultDataView(core, dataViews); + this.createDefaultDataView(core, dataViews).catch(() => {}); return {}; } diff --git a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts index 9753420234208..397145ac647b2 100644 --- a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDependencies } from '../plugin'; -export const registerApiKeyRoutes = ({ logger, router, security }: RouteDependencies) => { +export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/api_keys', @@ -16,6 +16,7 @@ export const registerApiKeyRoutes = ({ logger, router, security }: RouteDependen }, async (context, request, response) => { const { client } = (await context.core).elasticsearch; + const security = await getSecurity(); const user = security.authc.getCurrentUser(request); if (user) { const apiKeys = await client.asCurrentUser.security.getApiKey({ username: user.username }); @@ -37,6 +38,7 @@ export const registerApiKeyRoutes = ({ logger, router, security }: RouteDependen }, }, async (context, request, response) => { + const security = await getSecurity(); const result = await security.authc.apiKeys.create(request, request.body); if (result) { const apiKey = { ...result, beats_logstash_format: `${result.id}:${result.api_key}` }; diff --git a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts index 1e54a64642115..0cf303b677454 100644 --- a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts @@ -14,7 +14,7 @@ import { fetchIndices } from '../lib/indices/fetch_indices'; import { fetchIndex } from '../lib/indices/fetch_index'; import { RouteDependencies } from '../plugin'; -export const registerIndicesRoutes = ({ router, security }: RouteDependencies) => { +export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies) => { router.get( { path: '/internal/serverless_search/indices', @@ -28,6 +28,7 @@ export const registerIndicesRoutes = ({ router, security }: RouteDependencies) = }, async (context, request, response) => { const client = (await context.core).elasticsearch.client.asCurrentUser; + const security = await getSecurity(); const user = security.authc.getCurrentUser(request); if (!user) { diff --git a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts index 25a98d09a2bb8..e7a7b51b278d1 100644 --- a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts +++ b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts @@ -106,7 +106,7 @@ test(`it throws all other errors from the saved objects client when checking for simulateGetErrorCondition: true, }); - expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot( `"unit test: unexpected exception condition"` ); }); @@ -131,7 +131,7 @@ test(`it throws other errors if there is an error creating the default space`, a simulateCreateErrorCondition: true, }); - expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot( `"unit test: some other unexpected error"` ); }); diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts index 0c866de5bde66..e772ad389e8f9 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts @@ -24,7 +24,7 @@ const areObjectsUnique = (objects: SavedObjectIdentifier[]) => _.uniqBy(objects, (o: SavedObjectIdentifier) => `${o.type}:${o.id}`).length === objects.length; export function initCopyToSpacesApi(deps: ExternalRouteDeps) { - const { router, getSpacesService, usageStatsServicePromise, getStartServices } = deps; + const { router, getSpacesService, usageStatsServicePromise, getStartServices, log } = deps; const usageStatsClientPromise = usageStatsServicePromise.then(({ getClient }) => getClient()); router.post( @@ -96,14 +96,20 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) { } = request.body; const { headers } = request; - usageStatsClientPromise.then((usageStatsClient) => - usageStatsClient.incrementCopySavedObjects({ - headers, - createNewCopies, - overwrite, - compatibilityMode, - }) - ); + usageStatsClientPromise + .then((usageStatsClient) => + usageStatsClient.incrementCopySavedObjects({ + headers, + createNewCopies, + overwrite, + compatibilityMode, + }) + ) + .catch((err) => { + log.error( + `Failed to report usage statistics for the copy saved objects route: ${err.message}` + ); + }); try { const copySavedObjectsToSpaces = copySavedObjectsToSpacesFactory( @@ -198,13 +204,19 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) { request.body; const { headers } = request; - usageStatsClientPromise.then((usageStatsClient) => - usageStatsClient.incrementResolveCopySavedObjectsErrors({ - headers, - createNewCopies, - compatibilityMode, - }) - ); + usageStatsClientPromise + .then((usageStatsClient) => + usageStatsClient.incrementResolveCopySavedObjectsErrors({ + headers, + createNewCopies, + compatibilityMode, + }) + ) + .catch((err) => { + log.error( + `Failed to report usage statistics for the resolve copy saved objects errors route: ${err.message}` + ); + }); const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory(startServices.savedObjects, request); diff --git a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts index 725d0c87612fb..d708618f43870 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts @@ -12,7 +12,7 @@ import { wrapError } from '../../../lib/errors'; import { createLicensedRouteHandler } from '../../lib'; export function initDisableLegacyUrlAliasesApi(deps: ExternalRouteDeps) { - const { router, getSpacesService, usageStatsServicePromise } = deps; + const { router, getSpacesService, usageStatsServicePromise, log } = deps; const usageStatsClientPromise = usageStatsServicePromise.then(({ getClient }) => getClient()); router.post( @@ -35,9 +35,13 @@ export function initDisableLegacyUrlAliasesApi(deps: ExternalRouteDeps) { const { aliases } = request.body; - usageStatsClientPromise.then((usageStatsClient) => - usageStatsClient.incrementDisableLegacyUrlAliases() - ); + usageStatsClientPromise + .then((usageStatsClient) => usageStatsClient.incrementDisableLegacyUrlAliases()) + .catch((err) => { + log.error( + `Failed to report usage statistics for the disable legacy URL aliases route: ${err.message}` + ); + }); try { await spacesClient.disableLegacyUrlAliases(aliases); diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index 67c926229601b..0f689cd492e11 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -251,7 +251,7 @@ describe('#create', () => { const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); - expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting"` ); @@ -321,7 +321,7 @@ describe('#create', () => { [] ); - expect( + await expect( client.create({ ...spaceToCreate, disabledFeatures: ['some-feature'] }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to create Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled"` @@ -429,7 +429,7 @@ describe('#update', () => { ); const id = savedObject.id; - expect( + await expect( client.update(id, { ...spaceToUpdate, disabledFeatures: ['some-feature'] }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled"` @@ -475,7 +475,7 @@ describe('#delete', () => { const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); - expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot( `"The foo space cannot be deleted because it is reserved."` ); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx index f551d078314b6..005adb7be25ef 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/query_form_type_chooser.tsx @@ -50,7 +50,7 @@ export const QueryFormTypeChooser: React.FC = ({ onFormTypeSelect, }) => { const { uiSettings } = useTriggerUiActionServices(); - const isEsqlEnabled = uiSettings?.get('discover:enableESQL'); + const isEsqlEnabled = uiSettings?.get('enableESQL'); const formTypeItems = useMemo(() => { const items: Array<{ formType: SearchType; label: string; description: string }> = [ diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_shape_filters.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_shape_filters.test.ts index a133d65d871aa..5b48bd6512e9d 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_shape_filters.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_shape_filters.test.ts @@ -129,7 +129,7 @@ describe('getShapeFilters', () => { throw new Error('Simulated elasticsearch search error'); }, } as unknown as ElasticsearchClient; - expect(async () => { + await expect(async () => { await getShapeFilters(boundariesRequestMeta, mockEsClient); }).rejects.toThrow(); }); @@ -144,7 +144,7 @@ describe('getShapeFilters', () => { }; }, } as unknown as ElasticsearchClient; - expect(async () => { + await expect(async () => { await getShapeFilters(boundariesRequestMeta, mockEsClient); }).rejects.toThrow(); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_sir/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_sir/service.ts index 1c3972dd680b2..69836a5b95e29 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/servicenow_sir/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/servicenow_sir/service.ts @@ -38,7 +38,7 @@ export const createExternalService: ServiceFactory = ({ }); const _addObservable = async (data: Observable | Observable[], url: string) => { - snService.checkIfApplicationIsInstalled(); + await snService.checkIfApplicationIsInstalled(); const res = await request({ axios: axiosInstance, diff --git a/x-pack/plugins/task_manager/server/buffered_task_store.test.ts b/x-pack/plugins/task_manager/server/buffered_task_store.test.ts index 458b9d4c4bd2a..fa7a59478728d 100644 --- a/x-pack/plugins/task_manager/server/buffered_task_store.test.ts +++ b/x-pack/plugins/task_manager/server/buffered_task_store.test.ts @@ -135,7 +135,7 @@ describe('Buffered Task Store', () => { bufferedStore.update(tasks[2], { validate: true }), ]; expect(await results[0]).toMatchObject(tasks[0]); - expect(results[1]).rejects.toMatchInlineSnapshot(` + await expect(results[1]).rejects.toMatchInlineSnapshot(` Object { "error": Object { "error": "Oh no, something went terribly wrong", diff --git a/x-pack/plugins/task_manager/server/lib/bulk_operation_buffer.test.ts b/x-pack/plugins/task_manager/server/lib/bulk_operation_buffer.test.ts index 99bb7c5b84274..00dd7e00d27e2 100644 --- a/x-pack/plugins/task_manager/server/lib/bulk_operation_buffer.test.ts +++ b/x-pack/plugins/task_manager/server/lib/bulk_operation_buffer.test.ts @@ -69,28 +69,21 @@ describe('Bulk Operation Buffer', () => { const task3 = createTask(); const task4 = createTask(); - return new Promise((resolve) => { - Promise.all([bufferedUpdate(task1), bufferedUpdate(task2)]).then((_) => { - expect(bulkUpdate).toHaveBeenCalledTimes(1); - expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); - expect(bulkUpdate).not.toHaveBeenCalledWith([task3, task4]); - }); - - setTimeout(() => { - // on next tick - expect(bulkUpdate).toHaveBeenCalledTimes(1); - Promise.all([bufferedUpdate(task3), bufferedUpdate(task4)]).then((_) => { - expect(bulkUpdate).toHaveBeenCalledTimes(2); - expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); - }); - - setTimeout(() => { - // on next tick - expect(bulkUpdate).toHaveBeenCalledTimes(2); - resolve(); - }, bufferMaxDuration * 1.1); - }, bufferMaxDuration * 1.1); - }); + await Promise.all([bufferedUpdate(task1), bufferedUpdate(task2)]); + expect(bulkUpdate).toHaveBeenCalledTimes(1); + expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); + expect(bulkUpdate).not.toHaveBeenCalledWith([task3, task4]); + + await new Promise((resolve) => setTimeout(resolve, bufferMaxDuration * 1.1)); + // on next tick + expect(bulkUpdate).toHaveBeenCalledTimes(1); + await Promise.all([bufferedUpdate(task3), bufferedUpdate(task4)]); + expect(bulkUpdate).toHaveBeenCalledTimes(2); + expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); + + await new Promise((resolve) => setTimeout(resolve, bufferMaxDuration * 1.1)); + // on next tick + expect(bulkUpdate).toHaveBeenCalledTimes(2); }); test('batch updates are executed once queue hits a certain bound', async () => { @@ -110,20 +103,19 @@ describe('Bulk Operation Buffer', () => { const task4 = createTask(); const task5 = createTask(); - return Promise.all([ + await Promise.all([ bufferedUpdate(task1), bufferedUpdate(task2), bufferedUpdate(task3), bufferedUpdate(task4), - ]).then(() => { - expect(bulkUpdate).toHaveBeenCalledTimes(2); - expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); - expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); - return bufferedUpdate(task5).then((_) => { - expect(bulkUpdate).toHaveBeenCalledTimes(3); - expect(bulkUpdate).toHaveBeenCalledWith([task5]); - }); - }); + ]); + expect(bulkUpdate).toHaveBeenCalledTimes(2); + expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); + expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); + + await bufferedUpdate(task5); + expect(bulkUpdate).toHaveBeenCalledTimes(3); + expect(bulkUpdate).toHaveBeenCalledWith([task5]); }); test('queue upper bound is reset after each flush', async () => { @@ -142,24 +134,17 @@ describe('Bulk Operation Buffer', () => { const task3 = createTask(); const task4 = createTask(); - return Promise.all([bufferedUpdate(task1), bufferedUpdate(task2)]).then(() => { - expect(bulkUpdate).toHaveBeenCalledTimes(1); - expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); - - return new Promise((resolve) => { - const futureUpdates = Promise.all([bufferedUpdate(task3), bufferedUpdate(task4)]); + await Promise.all([bufferedUpdate(task1), bufferedUpdate(task2)]); + expect(bulkUpdate).toHaveBeenCalledTimes(1); + expect(bulkUpdate).toHaveBeenCalledWith([task1, task2]); - setTimeout(() => { - expect(bulkUpdate).toHaveBeenCalledTimes(1); + const futureUpdates = Promise.all([bufferedUpdate(task3), bufferedUpdate(task4)]); + await new Promise((resolve) => setTimeout(resolve, bufferMaxDuration / 2)); + expect(bulkUpdate).toHaveBeenCalledTimes(1); - futureUpdates.then(() => { - expect(bulkUpdate).toHaveBeenCalledTimes(2); - expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); - resolve(); - }); - }, bufferMaxDuration / 2); - }); - }); + await futureUpdates; + expect(bulkUpdate).toHaveBeenCalledTimes(2); + expect(bulkUpdate).toHaveBeenCalledWith([task3, task4]); }); test('handles both resolutions and rejections at individual task level', async () => { diff --git a/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts b/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts index 748e529ef1446..1270f1aedae6f 100644 --- a/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts +++ b/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts @@ -38,7 +38,7 @@ describe('removeIfExists', () => { const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuidv4()); ts.remove.mockRejectedValue(error); - expect(removeIfExists(ts, id)).rejects.toBe(error); + await expect(removeIfExists(ts, id)).rejects.toBe(error); expect(ts.remove).toHaveBeenCalledWith(id); }); diff --git a/x-pack/plugins/task_manager/server/lib/result_type.test.ts b/x-pack/plugins/task_manager/server/lib/result_type.test.ts index d35f00d30920c..4dc2f5f071278 100644 --- a/x-pack/plugins/task_manager/server/lib/result_type.test.ts +++ b/x-pack/plugins/task_manager/server/lib/result_type.test.ts @@ -17,12 +17,12 @@ describe(`Result`, () => { test(`unwraps Errs from the result`, async () => { const uniqueId = uuidv4(); - expect(unwrapPromise(Promise.resolve(asErr(uniqueId)))).rejects.toEqual(uniqueId); + await expect(unwrapPromise(Promise.resolve(asErr(uniqueId)))).rejects.toEqual(uniqueId); }); test(`unwraps Errs from the result when promise rejects`, async () => { const uniqueId = uuidv4(); - expect(unwrapPromise(Promise.reject(asErr(uniqueId)))).rejects.toEqual(uniqueId); + await expect(unwrapPromise(Promise.reject(asErr(uniqueId)))).rejects.toEqual(uniqueId); }); }); }); diff --git a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts index d588a001693b9..2d87f789128cd 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts @@ -64,7 +64,7 @@ export class TaskManagerMetricsCollector implements ITaskEventEmitter {}); } } diff --git a/x-pack/plugins/task_manager/server/mocks.ts b/x-pack/plugins/task_manager/server/mocks.ts index 45a2f117167ca..71638bc883681 100644 --- a/x-pack/plugins/task_manager/server/mocks.ts +++ b/x-pack/plugins/task_manager/server/mocks.ts @@ -29,7 +29,7 @@ const createStartMock = () => { runSoon: jest.fn(), ephemeralRunNow: jest.fn(), ensureScheduled: jest.fn(), - removeIfExists: jest.fn(), + removeIfExists: jest.fn().mockResolvedValue(Promise.resolve()), // it's a promise and there are some places where it's followed by `.catch()` supportsEphemeralTasks: jest.fn(), bulkUpdateSchedules: jest.fn(), bulkSchedule: jest.fn(), diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.ts b/x-pack/plugins/task_manager/server/polling/task_poller.ts index ff984a44f1aa0..570ecc8686693 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.ts @@ -72,7 +72,7 @@ export function createTaskPoller({ if (running) { // Set the next runCycle call timeoutId = setTimeout( - runCycle, + () => runCycle().catch(() => {}), Math.max(pollInterval - (Date.now() - start) + (pollIntervalDelay % pollInterval), 0) ); // Reset delay, it's designed to shuffle only once @@ -111,7 +111,7 @@ export function createTaskPoller({ start: () => { if (!running) { running = true; - runCycle(); + runCycle().catch(() => {}); // We need to subscribe shortly after start. Otherwise, the observables start emiting events // too soon for the task run statistics module to capture. setTimeout(() => subscribe(), 0); diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts index a3a047cd735c9..c0784f0458f72 100644 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ b/x-pack/plugins/task_manager/server/task_pool.ts @@ -198,7 +198,8 @@ export class TaskPool { }) .then(() => { this.tasksInPool.delete(taskRunner.taskExecutionId); - }); + }) + .catch(() => {}); } private handleFailureOfMarkAsRunning(task: TaskRunner, err: Error) { @@ -227,14 +228,17 @@ export class TaskPool { } } - private async cancelTask(task: TaskRunner) { - try { - this.logger.debug(`Cancelling task ${task.toString()}.`); - this.tasksInPool.delete(task.taskExecutionId); - await task.cancel(); - } catch (err) { - this.logger.error(`Failed to cancel task ${task.toString()}: ${err}`); - } + private cancelTask(task: TaskRunner) { + // internally async (without rejections), but public-facing is synchronous + (async () => { + try { + this.logger.debug(`Cancelling task ${task.toString()}.`); + this.tasksInPool.delete(task.taskExecutionId); + await task.cancel(); + } catch (err) { + this.logger.error(`Failed to cancel task ${task.toString()}: ${err}`); + } + })().catch(() => {}); } } diff --git a/x-pack/plugins/task_manager/server/task_scheduling.test.ts b/x-pack/plugins/task_manager/server/task_scheduling.test.ts index f4c8c0d6b0880..36de95416e1ca 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.test.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.test.ts @@ -836,7 +836,8 @@ describe('TaskScheduling', () => { }); describe('ephemeralRunNow', () => { - test('runs a task ephemerally', async () => { + // https://github.com/elastic/kibana/issues/181847 + test.skip('runs a task ephemerally', async () => { const ephemeralEvents$ = new Subject(); const ephemeralTask = taskManagerMock.createTask({ state: { @@ -880,7 +881,7 @@ describe('TaskScheduling', () => { ) ); - expect(result).resolves.toEqual({ id: 'v4uuid', state: { foo: 'bar' } }); + await expect(result).resolves.toEqual({ id: 'v4uuid', state: { foo: 'bar' } }); }); test('rejects ephemeral task if lifecycle returns an error', async () => { @@ -924,7 +925,7 @@ describe('TaskScheduling', () => { ) ); - expect(result).rejects.toMatchInlineSnapshot( + await expect(result).rejects.toMatchInlineSnapshot( `[Error: Ephemeral Task of type foo was rejected]` ); }); @@ -946,7 +947,7 @@ describe('TaskScheduling', () => { }); const result = taskScheduling.ephemeralRunNow(ephemeralTask); - expect(result).rejects.toMatchInlineSnapshot( + await expect(result).rejects.toMatchInlineSnapshot( `[Error: Ephemeral Task of type foo was rejected because ephemeral tasks are not supported]` ); }); diff --git a/x-pack/plugins/task_manager/server/task_scheduling.ts b/x-pack/plugins/task_manager/server/task_scheduling.ts index 7cb13182820b7..c57e54c76a98d 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.ts @@ -414,9 +414,11 @@ export class TaskScheduling { }); if (cancel) { - cancel.then(() => { - subscription.unsubscribe(); - }); + cancel + .then(() => { + subscription.unsubscribe(); + }) + .catch(() => {}); } }); } diff --git a/x-pack/plugins/threat_intelligence/server/plugin.ts b/x-pack/plugins/threat_intelligence/server/plugin.ts index d6fb0cb20aeee..91690b266d564 100644 --- a/x-pack/plugins/threat_intelligence/server/plugin.ts +++ b/x-pack/plugins/threat_intelligence/server/plugin.ts @@ -30,7 +30,7 @@ export class ThreatIntelligencePlugin implements IThreatIntelligencePlugin { ) { this.logger.debug('setup'); - core.getStartServices().then(([_, { data: dataStartService }]) => { + void core.getStartServices().then(([_, { data: dataStartService }]) => { const threatIntelligenceSearchStrategy = threatIntelligenceSearchStrategyProvider(dataStartService); diff --git a/x-pack/plugins/timelines/server/plugin.ts b/x-pack/plugins/timelines/server/plugin.ts index 2f8406b2b5646..619c66a05ee30 100644 --- a/x-pack/plugins/timelines/server/plugin.ts +++ b/x-pack/plugins/timelines/server/plugin.ts @@ -29,7 +29,7 @@ export class TimelinesPlugin const IndexFields = indexFieldsProvider(core.getStartServices); // Register search strategy - core.getStartServices().then(([_, depsStart]) => { + void core.getStartServices().then(([_, depsStart]) => { const TimelineSearchStrategy = timelineSearchStrategyProvider( depsStart.data, this.logger, diff --git a/x-pack/plugins/transform/server/plugin.ts b/x-pack/plugins/transform/server/plugin.ts index 6188a809c910c..6ca3eb92388f3 100644 --- a/x-pack/plugins/transform/server/plugin.ts +++ b/x-pack/plugins/transform/server/plugin.ts @@ -71,32 +71,41 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { ], }); - getStartServices().then(([coreStart, { dataViews, security: securityStart }]) => { - const license = new License({ - pluginId: PLUGIN.id, - minimumLicenseType: PLUGIN.minimumLicenseType, - defaultErrorMessage: i18n.translate('xpack.transform.licenseCheckErrorMessage', { - defaultMessage: 'License check failed', - }), - licensing, - logger: this.logger, - coreStart, - }); + registerRoutes({ + router: http.createRouter(), + getLicense: async () => { + const [coreStart] = await getStartServices(); + return new License({ + pluginId: PLUGIN.id, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.transform.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + licensing, + logger: this.logger, + coreStart, + }); + }, + getDataViewsStart: async () => { + const [, { dataViews }] = await getStartServices(); + return dataViews; + }, + getCoreStart: async () => { + const [coreStart] = await getStartServices(); + return coreStart; + }, + getSecurity: async () => { + const [, { security }] = await getStartServices(); + return security; + }, + }); - registerRoutes({ - router: http.createRouter(), - license, - dataViews, - coreStart, - security: securityStart, + if (usageCollection) { + registerCollector(usageCollection, async () => { + const [coreStart] = await getStartServices(); + return coreStart.savedObjects.getIndexForType('alert'); }); - - const alertIndex = coreStart.savedObjects.getIndexForType('alert'); - - if (usageCollection) { - registerCollector(usageCollection, alertIndex); - } - }); + } if (alerting) { registerTransformHealthRuleType({ diff --git a/x-pack/plugins/transform/server/routes/api/audit_messages/register_route.ts b/x-pack/plugins/transform/server/routes/api/audit_messages/register_route.ts index e5357034a1cbd..be1c0ef34ccae 100644 --- a/x-pack/plugins/transform/server/routes/api/audit_messages/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/audit_messages/register_route.ts @@ -17,7 +17,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms Audit Messages * @@ -42,10 +42,13 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute< - TransformIdParamSchema, - GetTransformAuditMessagesQuerySchema, - undefined - >(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute< + TransformIdParamSchema, + GetTransformAuditMessagesQuerySchema, + undefined + >(routeHandler)(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/delete_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/delete_transforms/register_route.ts index cdb58c02d9664..3646889f2c005 100644 --- a/x-pack/plugins/transform/server/routes/api/delete_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/delete_transforms/register_route.ts @@ -16,7 +16,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandlerFactory } from './route_handler_factory'; export function registerRoute(routeDependencies: RouteDependencies) { - const { router, license } = routeDependencies; + const { router, getLicense } = routeDependencies; /** * @apiGroup Transforms * @@ -40,8 +40,11 @@ export function registerRoute(routeDependencies: RouteDependencies) { }, }, }, - license.guardApiRoute( - routeHandlerFactory(routeDependencies) - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandlerFactory(routeDependencies) + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/delete_transforms/route_handler_factory.ts b/x-pack/plugins/transform/server/routes/api/delete_transforms/route_handler_factory.ts index c20dee6c68b98..c55fa798d24eb 100644 --- a/x-pack/plugins/transform/server/routes/api/delete_transforms/route_handler_factory.ts +++ b/x-pack/plugins/transform/server/routes/api/delete_transforms/route_handler_factory.ts @@ -24,13 +24,14 @@ export const routeHandlerFactory: ( DeleteTransformsRequestSchema, TransformRequestHandlerContext > = - ({ coreStart, dataViews }) => + ({ getCoreStart, getDataViewsStart }) => async (ctx, req, res) => { try { - const { savedObjects, elasticsearch } = coreStart; + const { savedObjects, elasticsearch } = await getCoreStart(); const savedObjectsClient = savedObjects.getScopedClient(req); const esClient = elasticsearch.client.asScoped(req).asCurrentUser; + const dataViews = await getDataViewsStart(); const dataViewsService = await dataViews.dataViewsServiceFactory( savedObjectsClient, esClient, diff --git a/x-pack/plugins/transform/server/routes/api/field_histograms/register_route.ts b/x-pack/plugins/transform/server/routes/api/field_histograms/register_route.ts index 4f8819b865f86..d1949f06e1ca5 100644 --- a/x-pack/plugins/transform/server/routes/api/field_histograms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/field_histograms/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { router.versioned .post({ path: addInternalBasePath('field_histograms/{dataViewTitle}'), @@ -31,8 +31,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute( - routeHandler - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/register_route.ts index 017aa98e76304..fc5128e0971b6 100644 --- a/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/register_route.ts @@ -14,7 +14,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandlerFactory } from './route_handler_factory'; export function registerRoute(routeDependencies: RouteDependencies) { - const { router, license } = routeDependencies; + const { router, getLicense } = routeDependencies; /** * @apiGroup Reauthorize transforms with API key generated from currently logged in user * @api {post} /internal/transform/reauthorize_transforms Post reauthorize transforms @@ -38,8 +38,11 @@ export function registerRoute(routeDependencies: RouteDependencies) { }, }, }, - license.guardApiRoute( - routeHandlerFactory(routeDependencies) - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandlerFactory(routeDependencies) + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts b/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts index acefdec0257a9..aa63936a3f6e7 100644 --- a/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts +++ b/x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts @@ -26,7 +26,9 @@ export const routeHandlerFactory: ( StartTransformsRequestSchema, TransformRequestHandlerContext > = (routeDependencies) => async (ctx, req, res) => { - const { coreStart, security: securityStart } = routeDependencies; + const { getCoreStart, getSecurity } = routeDependencies; + const coreStart = await getCoreStart(); + const securityStart = await getSecurity(); try { const transformsInfo = req.body; diff --git a/x-pack/plugins/transform/server/routes/api/reset_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/reset_transforms/register_route.ts index cae6245455810..adbe55b8e828d 100644 --- a/x-pack/plugins/transform/server/routes/api/reset_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/reset_transforms/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -39,6 +39,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/schedule_now_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/schedule_now_transforms/register_route.ts index cf19b56c316f1..0ec8808ae2ecf 100644 --- a/x-pack/plugins/transform/server/routes/api/schedule_now_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/schedule_now_transforms/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -39,6 +39,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/start_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/start_transforms/register_route.ts index 5cba3a5a01d70..5ce3de48e63a3 100644 --- a/x-pack/plugins/transform/server/routes/api/start_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/start_transforms/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -39,6 +39,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/stop_transforms/register_route.ts b/x-pack/plugins/transform/server/routes/api/stop_transforms/register_route.ts index df9498f9ae033..b859ee4743c40 100644 --- a/x-pack/plugins/transform/server/routes/api/stop_transforms/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/stop_transforms/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -39,6 +39,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_all/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_all/register_route.ts index e808e0e8fafc2..29cbd34bffd68 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_all/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_all/register_route.ts @@ -13,7 +13,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -29,13 +29,16 @@ export function registerRoute({ router, license }: RouteDependencies) { path: addInternalBasePath('transforms'), access: 'internal', }) - .addVersion( + .addVersion( { version: '1', validate: false, }, - license.guardApiRoute( - routeHandler - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_create/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_create/register_route.ts index f6be9e18893f2..9237977118d05 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_create/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_create/register_route.ts @@ -25,7 +25,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandlerFactory } from './route_handler_factory'; export function registerRoute(routeDependencies: RouteDependencies) { - const { router, license } = routeDependencies; + const { router, getLicense } = routeDependencies; /** * @apiGroup Transforms @@ -54,10 +54,13 @@ export function registerRoute(routeDependencies: RouteDependencies) { }, }, }, - license.guardApiRoute< - TransformIdParamSchema, - DataViewCreateQuerySchema, - PutTransformsRequestSchema - >(routeHandlerFactory(routeDependencies)) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute< + TransformIdParamSchema, + DataViewCreateQuerySchema, + PutTransformsRequestSchema + >(routeHandlerFactory(routeDependencies))(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_create/route_handler_factory.ts b/x-pack/plugins/transform/server/routes/api/transforms_create/route_handler_factory.ts index 8ca98efe3cb54..0baa019808e8b 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_create/route_handler_factory.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_create/route_handler_factory.ts @@ -31,7 +31,9 @@ export const routeHandlerFactory: ( PutTransformsRequestSchema, TransformRequestHandlerContext > = (routeDependencies) => async (ctx, req, res) => { - const { coreStart, dataViews } = routeDependencies; + const { getCoreStart, getDataViewsStart } = routeDependencies; + const coreStart = await getCoreStart(); + const dataViews = await getDataViewsStart(); const { transformId } = req.params; const { createDataView, timeFieldName } = req.query; diff --git a/x-pack/plugins/transform/server/routes/api/transforms_nodes/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_nodes/register_route.ts index f1a75d9f9ef28..701306ff6481c 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_nodes/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_nodes/register_route.ts @@ -11,7 +11,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandlerFactory } from './route_handler_factory'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transform Nodes * @@ -29,6 +29,13 @@ export function registerRoute({ router, license }: RouteDependencies) { version: '1', validate: false, }, - license.guardApiRoute(routeHandlerFactory(license)) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute(routeHandlerFactory(license))( + ctx, + request, + response + ); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_nodes/route_handler_factory.ts b/x-pack/plugins/transform/server/routes/api/transforms_nodes/route_handler_factory.ts index c512bfd58594a..10171f948c2ad 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_nodes/route_handler_factory.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_nodes/route_handler_factory.ts @@ -9,10 +9,9 @@ import Boom from '@hapi/boom'; import type { RequestHandler } from '@kbn/core/server'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import type { License } from '../../../services'; import { NODES_INFO_PRIVILEGES } from '../../../../common/constants'; -import type { RouteDependencies } from '../../../types'; - import { wrapError, wrapEsError } from '../../utils/error_utils'; const NODE_ROLES = 'roles'; @@ -33,7 +32,7 @@ export const isNodes = (arg: unknown): arg is Nodes => { }; export const routeHandlerFactory: ( - license: RouteDependencies['license'] + license: License ) => RequestHandler = (license) => async (ctx, req, res) => { try { const esClient = (await ctx.core).elasticsearch.client; diff --git a/x-pack/plugins/transform/server/routes/api/transforms_preview/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_preview/register_route.ts index 646f92271243b..55e1446dfe5aa 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_preview/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_preview/register_route.ts @@ -13,7 +13,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -37,6 +37,11 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute( + routeHandler + )(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_single/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_single/register_route.ts index e79446d3962c0..c13c2b996dc7f 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_single/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_single/register_route.ts @@ -15,7 +15,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -39,6 +39,13 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute(routeHandler)( + ctx, + request, + response + ); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_stats_all/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_stats_all/register_route.ts index ef0bf70ec96d1..8aa59649af1d4 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_stats_all/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_stats_all/register_route.ts @@ -17,7 +17,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -43,10 +43,13 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute< - estypes.TransformGetTransformStatsResponse, - GetTransformStatsQuerySchema, - undefined - >(routeHandler) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute< + estypes.TransformGetTransformStatsResponse, + GetTransformStatsQuerySchema, + undefined + >(routeHandler)(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_stats_single/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_stats_single/register_route.ts index a0e86fe7bf01c..9f7424cd62acf 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_stats_single/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_stats_single/register_route.ts @@ -19,7 +19,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -44,8 +44,13 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute( - routeHandler - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute< + TransformIdParamSchema, + GetTransformStatsQuerySchema, + undefined + >(routeHandler)(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/routes/api/transforms_update/register_route.ts b/x-pack/plugins/transform/server/routes/api/transforms_update/register_route.ts index 366b4ea32fc92..a90ce9395bfce 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_update/register_route.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_update/register_route.ts @@ -19,7 +19,7 @@ import type { RouteDependencies } from '../../../types'; import { routeHandler } from './route_handler'; -export function registerRoute({ router, license }: RouteDependencies) { +export function registerRoute({ router, getLicense }: RouteDependencies) { /** * @apiGroup Transforms * @@ -45,8 +45,13 @@ export function registerRoute({ router, license }: RouteDependencies) { }, }, }, - license.guardApiRoute( - routeHandler - ) + async (ctx, request, response) => { + const license = await getLicense(); + return license.guardApiRoute< + TransformIdParamSchema, + undefined, + PostTransformsUpdateRequestSchema + >(routeHandler)(ctx, request, response); + } ); } diff --git a/x-pack/plugins/transform/server/types.ts b/x-pack/plugins/transform/server/types.ts index d32f295fdf679..e5c44aafe163a 100644 --- a/x-pack/plugins/transform/server/types.ts +++ b/x-pack/plugins/transform/server/types.ts @@ -32,8 +32,8 @@ export interface PluginStartDependencies { export interface RouteDependencies { router: IRouter; - license: License; - coreStart: CoreStart; - dataViews: DataViewsServerPluginStart; - security?: SecurityPluginStart; + getLicense: () => Promise; + getCoreStart: () => Promise; + getDataViewsStart: () => Promise; + getSecurity: () => Promise; } diff --git a/x-pack/plugins/transform/server/usage/collector.ts b/x-pack/plugins/transform/server/usage/collector.ts index c2ee09ad983c1..98979375d4d49 100644 --- a/x-pack/plugins/transform/server/usage/collector.ts +++ b/x-pack/plugins/transform/server/usage/collector.ts @@ -22,7 +22,10 @@ export interface TransformAlertsUsageData { }; } -export function registerCollector(usageCollection: UsageCollectionSetup, alertIndex: string) { +export function registerCollector( + usageCollection: UsageCollectionSetup, + getAlertIndex: () => Promise +) { const collector = usageCollection.makeUsageCollector({ type: 'transform', schema: { @@ -62,7 +65,7 @@ export function registerCollector(usageCollection: UsageCollectionSetup, alertIn }; }>( { - index: alertIndex, + index: await getAlertIndex(), size: 10000, query: { bool: { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 60c21cfc74cdd..7e7d4218db8fe 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2301,8 +2301,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "Permet de masquer la colonne ''Time'' dans Discover et dans toutes les recherches enregistrées des tableaux de bord.", "discover.advancedSettings.docTableHideTimeColumnTitle": "Masquer la colonne ''Time''", "discover.advancedSettings.documentExplorerLinkText": "Explorateur de documents", - "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableESQLTitle": "Activer ES|QL", + "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "textBasedLanguages.advancedSettings.enableESQLTitle": "Activer ES|QL", "discover.advancedSettings.fieldsPopularLimitText": "Les N champs les plus populaires à afficher", "discover.advancedSettings.fieldsPopularLimitTitle": "Limite de champs populaires", "discover.advancedSettings.maxDocFieldsDisplayedText": "Le nombre maximal de champs renvoyés dans le résumé du document", @@ -26460,7 +26460,6 @@ "xpack.ml.datavisualizer.selector.startTrialButtonLabel": "Commencer l'essai", "xpack.ml.datavisualizer.selector.startTrialTitle": "Commencer l'essai", "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "Utilisez les requêtes ES|QL pour visualiser les informations de n'importe quel ensemble de données.", - "xpack.ml.datavisualizer.selector.technicalPreviewBadge.titleMsg": "ES|QL est en version d'évaluation technique.", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "Sélectionner un fichier", "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "Utiliser ES|QL", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "Abonnement Platinum ou Enterprise", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6ea7973b93a11..60e15dd80a172 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2299,8 +2299,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "Discover と、ダッシュボードのすべての保存された検索で、「時刻」列を非表示にします。", "discover.advancedSettings.docTableHideTimeColumnTitle": "「時刻」列を非表示", "discover.advancedSettings.documentExplorerLinkText": "ドキュメントエクスプローラー", - "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableESQLTitle": "ES|QLを有効化", + "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "textBasedLanguages.advancedSettings.enableESQLTitle": "ES|QLを有効化", "discover.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップNを表示します", "discover.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", "discover.advancedSettings.maxDocFieldsDisplayedText": "ドキュメント概要でレンダリングされるフィールドの最大数", @@ -26433,7 +26433,6 @@ "xpack.ml.datavisualizer.selector.startTrialButtonLabel": "トライアルを開始", "xpack.ml.datavisualizer.selector.startTrialTitle": "トライアルを開始", "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "ES|QLクエリを使用して、データセットに関する情報を可視化します。", - "xpack.ml.datavisualizer.selector.technicalPreviewBadge.titleMsg": "ES|QLはテクニカルプレビューです。", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "ファイルを選択", "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "ES|QLを使用", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "PlatinumまたはEnterprise サブスクリプション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4742b2168dc60..430a3b8d46916 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2303,8 +2303,8 @@ "discover.advancedSettings.docTableHideTimeColumnText": "在 Discover 中和仪表板上的所有已保存搜索中隐藏“时间”列。", "discover.advancedSettings.docTableHideTimeColumnTitle": "隐藏“时间”列", "discover.advancedSettings.documentExplorerLinkText": "Document Explorer", - "discover.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", - "discover.advancedSettings.enableESQLTitle": "启用 ES|QL", + "textBasedLanguages.advancedSettings.enableESQL.discussLinkText": "discuss.elastic.co/c/elastic-stack/kibana", + "textBasedLanguages.advancedSettings.enableESQLTitle": "启用 ES|QL", "discover.advancedSettings.fieldsPopularLimitText": "要显示的排名前 N 最常见字段", "discover.advancedSettings.fieldsPopularLimitTitle": "常见字段限制", "discover.advancedSettings.maxDocFieldsDisplayedText": "在文档摘要中渲染的最大字段数目", @@ -26471,7 +26471,6 @@ "xpack.ml.datavisualizer.selector.startTrialButtonLabel": "开始试用", "xpack.ml.datavisualizer.selector.startTrialTitle": "开始试用", "xpack.ml.datavisualizer.selector.technicalPreviewBadge.contentMsg": "使用 ES|QL 查询可视化有关任何数据集的信息。", - "xpack.ml.datavisualizer.selector.technicalPreviewBadge.titleMsg": "ES|QL 处于技术预览状态。", "xpack.ml.datavisualizer.selector.uploadFileButtonLabel": "选择文件", "xpack.ml.datavisualizer.selector.useESQLButtonLabel": "使用 ES|QL", "xpack.ml.datavisualizer.startTrial.subscriptionsLinkText": "白金级或企业级订阅", diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index 9dfc32b6ee930..efc802dc73d3c 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -50,7 +50,7 @@ describe('timeSeriesQuery', () => { }); it('fails as expected when the query params are invalid', async () => { - expect( + await expect( timeSeriesQuery({ ...params, query: { ...params.query, dateStart: 'x' } }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid date format for dateStart: \\"x\\""`); }); diff --git a/x-pack/plugins/triggers_actions_ui/server/plugin.ts b/x-pack/plugins/triggers_actions_ui/server/plugin.ts index 04de44a088ed5..b5cafb0571482 100644 --- a/x-pack/plugins/triggers_actions_ui/server/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/server/plugin.ts @@ -53,14 +53,15 @@ export class TriggersActionsPlugin implements Plugin plugins.alerting !== undefined ); - core.getStartServices().then(([_, pluginStart]) => { - createConfigRoute({ - logger: this.logger, - router, - baseRoute: BASE_TRIGGERS_ACTIONS_UI_API_PATH, - alertingConfig: plugins.alerting.getConfig, - getRulesClientWithRequest: pluginStart.alerting.getRulesClientWithRequest, - }); + createConfigRoute({ + logger: this.logger, + router, + baseRoute: BASE_TRIGGERS_ACTIONS_UI_API_PATH, + alertingConfig: plugins.alerting.getConfig, + getRulesClientWithRequest: async (request) => { + const [, pluginStart] = await core.getStartServices(); + return pluginStart.alerting.getRulesClientWithRequest(request); + }, }); } diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts index 8918e77795520..c951a5f9d34d2 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts @@ -55,7 +55,7 @@ describe('createConfigRoute', () => { maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, }), - getRulesClientWithRequest: () => mockRulesClient, + getRulesClientWithRequest: async () => mockRulesClient, }); const [config, handler] = router.get.mock.calls[0]; @@ -89,7 +89,7 @@ describe('createConfigRoute', () => { maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, }), - getRulesClientWithRequest: () => mockRulesClient, + getRulesClientWithRequest: async () => mockRulesClient, }); const [config, handler] = router.get.mock.calls[0]; diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.ts index 1cc328c0ae5ad..c0212d7dc824f 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.ts @@ -23,7 +23,7 @@ export interface ConfigRouteOpts { // alertingConfig is a function because "isUsingSecurity" is pulled from the license // state which gets populated after plugin setup(). alertingConfig: () => AlertingRulesConfig; - getRulesClientWithRequest: (request: KibanaRequest) => RulesClientApi; + getRulesClientWithRequest: (request: KibanaRequest) => Promise; } export function createConfigRoute({ @@ -48,7 +48,7 @@ export function createConfigRoute({ res: KibanaResponseFactory ): Promise { // Check that user has access to at least one rule type - const rulesClient = getRulesClientWithRequest(req); + const rulesClient = await getRulesClientWithRequest(req); const ruleTypes = Array.from(await rulesClient.listRuleTypes()); if (ruleTypes.length > 0) { return res.ok({ body: alertingConfig() }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts index 46fcf6a4cec5b..902c7e83c59b4 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts @@ -6,6 +6,7 @@ */ import { IClusterClient, Logger, SavedObjectsClientContract, FakeRequest } from '@kbn/core/server'; +import { exhaustMap, Subject, takeUntil, timer } from 'rxjs'; import moment from 'moment'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; @@ -41,9 +42,8 @@ const WORKER_PADDING_MS = 1000; */ export class ReindexWorker { private static workerSingleton?: ReindexWorker; - private continuePolling: boolean = false; + private readonly stop$ = new Subject(); private updateOperationLoopRunning: boolean = false; - private timeout?: NodeJS.Timeout; private inProgressOps: ReindexSavedObject[] = []; private readonly reindexService: ReindexService; private readonly log: Logger; @@ -95,12 +95,16 @@ export class ReindexWorker { } /** - * Begins loop (1) to begin checking for in progress reindex operations. + * Begins loop checking for in progress reindex operations. */ public start = () => { this.log.debug('Starting worker...'); - this.continuePolling = true; - this.pollForOperations(); + timer(0, POLL_INTERVAL) + .pipe( + takeUntil(this.stop$), + exhaustMap(() => this.pollForOperations()) + ) + .subscribe(); }; /** @@ -108,19 +112,18 @@ export class ReindexWorker { */ public stop = () => { this.log.debug('Stopping worker...'); - if (this.timeout) { - clearTimeout(this.timeout); - } - + this.stop$.next(); this.updateOperationLoopRunning = false; - this.continuePolling = false; }; /** * Should be called immediately after this server has started a new reindex operation. */ public forceRefresh = () => { - this.refresh(); + // We know refresh won't throw, but just in case it does in the future + this.refresh().catch((error) => { + this.log.warn(`Failed to force refresh the reindex operations: ${error}`); + }); }; /** @@ -153,6 +156,8 @@ export class ReindexWorker { await new Promise((resolve) => setTimeout(resolve, WORKER_PADDING_MS)); } } + } catch (error) { + this.log.warn(`Failed to update reindex operations: ${error}`); } finally { this.updateOperationLoopRunning = false; } @@ -162,10 +167,6 @@ export class ReindexWorker { this.log.debug(`Polling for reindex operations`); await this.refresh(); - - if (this.continuePolling) { - this.timeout = setTimeout(this.pollForOperations, POLL_INTERVAL); - } }; private getCredentialScopedReindexService = (credential: Credential) => { @@ -194,7 +195,7 @@ export class ReindexWorker { firstOpInQueue.attributes.indexName ); // Re-associate the credentials - this.credentialStore.update({ + await this.credentialStore.update({ reindexOp: firstOpInQueue, security: this.security, credential, @@ -213,7 +214,7 @@ export class ReindexWorker { await this.updateInProgressOps(); // If there are operations in progress and we're not already updating operations, kick off the update loop if (!this.updateOperationLoopRunning) { - this.startUpdateOperationLoop(); + await this.startUpdateOperationLoop(); } }; @@ -250,7 +251,7 @@ export class ReindexWorker { reindexOp = await swallowExceptions(service.processNextStep, this.log)(reindexOp); // Update credential store with most recent state. - this.credentialStore.update({ reindexOp, security: this.security, credential }); + await this.credentialStore.update({ reindexOp, security: this.security, credential }); }; } diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 6dfb65be85d7b..a2753279e8f69 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -147,7 +147,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { registerRoutes(dependencies, this.getWorker.bind(this)); if (usageCollection) { - getStartServices().then(([{ elasticsearch }]) => { + void getStartServices().then(([{ elasticsearch }]) => { registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts index cb933c7cdf6ba..47f766f328471 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts @@ -19,7 +19,8 @@ const findTestUtils = ( supertest: SuperTest, supertestWithoutAuth: any ) => { - describe(describeType, () => { + // Failing: See https://github.com/elastic/kibana/issues/182263 + describe.skip(describeType, () => { afterEach(() => objectRemover.removeAll()); for (const scenario of UserAtSpaceScenarios) { diff --git a/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts b/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts index fe9967683ec8d..5a98ec708bcf3 100644 --- a/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts +++ b/x-pack/test/apm_api_integration/tests/assistant/obs_alert_details_context.spec.ts @@ -8,6 +8,7 @@ import moment from 'moment'; import { log, apm, generateShortId, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; +import { LogCategories } from '@kbn/apm-plugin/server/routes/assistant_functions/get_log_categories'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { SupertestReturnType } from '../../common/apm_api_supertest'; @@ -26,10 +27,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const range = timerange(start, end); describe('when no traces or logs are available', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -39,11 +40,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns nothing', () => { - expect(response.body).to.eql({ - serviceChangePoints: [], - exitSpanChangePoints: [], - anomalies: [], - }); + expect(response.body.context).to.eql([]); }); }); @@ -61,10 +58,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when no params are specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -73,26 +70,21 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - it('returns no service summary', async () => { - expect(response.body.serviceSummary).to.be(undefined); - }); - - it('returns no downstream dependencies', async () => { - expect(response.body.downstreamDependencies ?? []).to.eql([]); - }); - - it('returns 1 log category', async () => { - expect(response.body.logCategories?.map(({ errorCategory }) => errorCategory)).to.eql([ - 'Error message from container my-container-a', - ]); + it('returns only 1 log category', async () => { + expect(response.body.context).to.have.length(1); + expect( + (response.body.context[0]?.data as LogCategories)?.map( + ({ errorCategory }: { errorCategory: string }) => errorCategory + ) + ).to.eql(['Error message from container my-container-a']); }); }); describe('when service name is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -103,7 +95,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns service summary', () => { - expect(response.body.serviceSummary).to.eql({ + const serviceSummary = response.body.context.find( + ({ key }) => key === 'serviceSummary' + ); + expect(serviceSummary?.data).to.eql({ 'service.name': 'Backend', 'service.environment': ['production'], 'agent.name': 'java', @@ -117,7 +112,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns downstream dependencies', async () => { - expect(response.body.downstreamDependencies).to.eql([ + const downstreamDependencies = response.body.context.find( + ({ key }) => key === 'downstreamDependencies' + ); + expect(downstreamDependencies?.data).to.eql([ { 'span.destination.service.resource': 'elasticsearch', 'span.type': 'db', @@ -127,9 +125,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns log categories', () => { - expect(response.body.logCategories).to.have.length(1); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); - const logCategory = response.body.logCategories?.[0]; + const logCategory = (logCategories?.data as LogCategories)?.[0]; expect(logCategory?.sampleMessage).to.match( /Error message #\d{16} from container my-container-a/ ); @@ -139,10 +138,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when container id is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -153,7 +152,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns service summary', () => { - expect(response.body.serviceSummary).to.eql({ + const serviceSummary = response.body.context.find( + ({ key }) => key === 'serviceSummary' + ); + expect(serviceSummary?.data).to.eql({ 'service.name': 'Backend', 'service.environment': ['production'], 'agent.name': 'java', @@ -167,7 +169,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns downstream dependencies', async () => { - expect(response.body.downstreamDependencies).to.eql([ + const downstreamDependencies = response.body.context.find( + ({ key }) => key === 'downstreamDependencies' + ); + expect(downstreamDependencies?.data).to.eql([ { 'span.destination.service.resource': 'elasticsearch', 'span.type': 'db', @@ -177,9 +182,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns log categories', () => { - expect(response.body.logCategories).to.have.length(1); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); - const logCategory = response.body.logCategories?.[0]; + const logCategory = (logCategories?.data as LogCategories)?.[0]; expect(logCategory?.sampleMessage).to.match( /Error message #\d{16} from container my-container-a/ ); @@ -189,10 +195,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when non-existing container id is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -203,20 +209,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns nothing', () => { - expect(response.body).to.eql({ - logCategories: [], - serviceChangePoints: [], - exitSpanChangePoints: [], - anomalies: [], - }); + expect(response.body.context).to.eql([]); }); }); describe('when non-existing service.name is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -227,7 +228,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns empty service summary', () => { - expect(response.body.serviceSummary).to.eql({ + const serviceSummary = response.body.context.find( + ({ key }) => key === 'serviceSummary' + ); + expect(serviceSummary?.data).to.eql({ 'service.name': 'non-existing-service', 'service.environment': [], instances: 1, @@ -238,11 +242,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns no downstream dependencies', async () => { - expect(response.body.downstreamDependencies).to.eql([]); + const downstreamDependencies = response.body.context.find( + ({ key }) => key === 'downstreamDependencies' + ); + expect(downstreamDependencies).to.eql(undefined); }); it('returns log categories', () => { - expect(response.body.logCategories).to.have.length(1); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); }); }); }); @@ -276,10 +284,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when no params are specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -289,22 +297,27 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns no service summary', async () => { - expect(response.body.serviceSummary).to.be(undefined); + const serviceSummary = response.body.context.find( + ({ key }) => key === 'serviceSummary' + ); + expect(serviceSummary).to.be(undefined); }); it('returns 1 log category', async () => { - expect(response.body.logCategories?.map(({ errorCategory }) => errorCategory)).to.eql([ - 'Error message from service', - 'Error message from container my-container-c', - ]); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect( + (logCategories?.data as LogCategories)?.map( + ({ errorCategory }: { errorCategory: string }) => errorCategory + ) + ).to.eql(['Error message from service', 'Error message from container my-container-c']); }); }); describe('when service name is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -315,9 +328,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns log categories', () => { - expect(response.body.logCategories).to.have.length(1); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); - const logCategory = response.body.logCategories?.[0]; + const logCategory = (logCategories?.data as LogCategories)?.[0]; expect(logCategory?.sampleMessage).to.match( /Error message #\d{16} from service Backend/ ); @@ -327,10 +341,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when container id is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -341,9 +355,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns log categories', () => { - expect(response.body.logCategories).to.have.length(1); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); - const logCategory = response.body.logCategories?.[0]; + const logCategory = (logCategories?.data as LogCategories)?.[0]; expect(logCategory?.sampleMessage).to.match( /Error message #\d{16} from service Backend/ ); @@ -353,10 +368,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('when non-existing service.name is specified', async () => { - let response: SupertestReturnType<'GET /internal/apm/assistant/get_obs_alert_details_context'>; + let response: SupertestReturnType<'GET /internal/apm/assistant/alert_details_contextual_insights'>; before(async () => { response = await apmApiClient.writeUser({ - endpoint: 'GET /internal/apm/assistant/get_obs_alert_details_context', + endpoint: 'GET /internal/apm/assistant/alert_details_contextual_insights', params: { query: { alert_started_at: new Date(end).toISOString(), @@ -367,7 +382,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns empty service summary', () => { - expect(response.body.serviceSummary).to.eql({ + const serviceSummary = response.body.context.find( + ({ key }) => key === 'serviceSummary' + ); + expect(serviceSummary?.data).to.eql({ 'service.name': 'non-existing-service', 'service.environment': [], instances: 1, @@ -378,9 +396,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('does not return log categories', () => { - expect(response.body.logCategories?.map(({ errorCategory }) => errorCategory)).to.eql([ - 'Error message from container my-container-c', - ]); + const logCategories = response.body.context.find(({ key }) => key === 'logCategories'); + expect(logCategories?.data).to.have.length(1); + + expect( + (logCategories?.data as LogCategories)?.map( + ({ errorCategory }: { errorCategory: string }) => errorCategory + ) + ).to.eql(['Error message from container my-container-c']); }); }); }); diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index e2cb7962db7ae..b1eadf89a5297 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const monacoEditor = getService('monacoEditor'); const defaultSettings = { - 'discover:enableESQL': true, + enableESQL: true, }; async function setDiscoverTimeRange() { diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index d0d56da9091d0..a6f936e43bf37 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -497,9 +497,7 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertExamplesList(fieldName, expectedExamplesCount); - await testSubjects.existOrFail( - this.detailsSelector(fieldName, 'dataVisualizerEmbeddedMapContent') - ); + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mapContainer')); await this.ensureDetailsClosed(fieldName); } diff --git a/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts b/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts index 13fd8307a24a3..b13505a081ae9 100644 --- a/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts +++ b/x-pack/test/functional_cors/plugins/kibana_cors_test/server/plugin.ts @@ -75,12 +75,12 @@ export class CorsTestPlugin implements Plugin { return h.response(renderBody(kibanaUrl)); }, }); - server.start(); + void server.start(); } public stop() { if (this.server) { - this.server.stop(); + void this.server.stop(); } } } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index a6b5af188c260..3aee927ffcfd2 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -987,7 +987,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('Execution log', () => { + // FLAKY: https://github.com/elastic/kibana/issues/173666 + describe.skip('Execution log', () => { const testRunUuid = uuidv4(); let rule: any; diff --git a/x-pack/test/monitoring_api_integration/apis/apm/index.ts b/x-pack/test/monitoring_api_integration/apis/apm/index.ts index 0171da7e6b83a..ddb2109173f12 100644 --- a/x-pack/test/monitoring_api_integration/apis/apm/index.ts +++ b/x-pack/test/monitoring_api_integration/apis/apm/index.ts @@ -9,7 +9,8 @@ import { FtrProviderContext } from '../../../api_integration/ftr_provider_contex import { installPackage } from '../../packages'; export default function ({ loadTestFile, getService }: FtrProviderContext) { - describe('APM', () => { + // FLAKY: https://github.com/elastic/kibana/issues/177142 + describe.skip('APM', () => { before(() => installPackage(getService('supertest'), 'beat')); loadTestFile(require.resolve('./overview')); diff --git a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/server/init_routes.ts b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/server/init_routes.ts index 7d5118c5d93d3..e4a8bc76a8908 100644 --- a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/server/init_routes.ts +++ b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/server/init_routes.ts @@ -75,9 +75,9 @@ export function initRoutes( }, Promise.resolve(undefined)); return res.ok({ - body: await new Promise((resolve) => { + body: await new Promise((resolve, reject) => { setTimeout(() => { - performanceApi.endCapture().then((perf) => resolve(perf)); + performanceApi.endCapture().then((perf) => resolve(perf), reject); }, durationInSeconds * 1000 + 10000 /* wait extra 10s to drain queue */); }), }); diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts index e200056795616..124fcea644c79 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts @@ -35,24 +35,32 @@ export default function (ftrContext: FtrProviderContext) { authorized: { httpCode: 200, expectResponse: ({ body }) => { - expect(body).to.eql({ - tags: [ - { - id: 'default-space-tag-1', - name: 'tag-1', - description: 'Tag 1 in default space', - color: '#FF00FF', - managed: false, - }, - { - id: 'default-space-tag-2', - name: 'tag-2', - description: 'Tag 2 in default space', - color: '#77CC11', - managed: false, - }, - ], - }); + if (!Array.isArray(body.tags)) { + throw new Error('Expected body.tags to be an array'); + } + + const tags = (body.tags as [{ id: string }]) + // sort the tags by ID alphabetically + .sort((a, b) => { + return a.id.localeCompare(b.id); + }); + + expect(tags).to.eql([ + { + id: 'default-space-tag-1', + name: 'tag-1', + description: 'Tag 1 in default space', + color: '#FF00FF', + managed: false, + }, + { + id: 'default-space-tag-2', + name: 'tag-2', + description: 'Tag 2 in default space', + color: '#77CC11', + managed: false, + }, + ]); }, }, unauthorized: { diff --git a/x-pack/test/saved_object_tagging/functional/tests/discover_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/discover_integration.ts index 8258b4570ed9b..365ce9a715816 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/discover_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/discover_integration.ts @@ -48,6 +48,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const searchTitles = await Promise.all( searchTitleWrappers.map((entry) => entry.getVisibleText()) ); + searchTitles.sort(); + savedSearchTitles.sort(); expect(searchTitles).to.eql(savedSearchTitles); }); }; diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/generate_anomaly_alerts.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/generate_anomaly_alerts.ts index 8d93f1349cae6..75c441bc21cd9 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/generate_anomaly_alerts.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/generate_anomaly_alerts.ts @@ -65,9 +65,8 @@ function createTestJobAndDatafeed() { export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - const pageObjects = getPageObjects(['triggersActionsUI']); + const pageObjects = getPageObjects(['triggersActionsUI', 'header']); const commonScreenshots = getService('commonScreenshots'); - const browser = getService('browser'); const actions = getService('actions'); const testSubjects = getService('testSubjects'); @@ -105,16 +104,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('alert flyout screenshots', async () => { await ml.navigation.navigateToAlertsAndAction(); await pageObjects.triggersActionsUI.clickCreateAlertButton(); - await ml.alerting.setRuleName('test-ecommerce'); - const searchBox = await testSubjects.find('ruleSearchField'); - await searchBox.click(); - await searchBox.clearValue(); - await searchBox.type('ml'); - await searchBox.pressKeys(browser.keys.ENTER); - await ml.testExecution.logTestStep('take screenshot'); - await commonScreenshots.takeScreenshot('ml-rule', screenshotDirectories, 1920, 1400); - + await ml.testExecution.logTestStep('Create anomaly detection jobs health rule'); await ml.alerting.selectAnomalyDetectionJobHealthAlertType(); + await pageObjects.header.waitUntilLoadingHasFinished(); + await ml.alerting.setRuleName('test-ecommerce'); await ml.alerting.selectJobs([testJobId]); await ml.testExecution.logTestStep('take screenshot'); await commonScreenshots.takeScreenshot( @@ -137,9 +130,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); await ml.alerting.clickCancelSaveRuleButton(); + await ml.testExecution.logTestStep('Create anomaly detection rule'); await pageObjects.triggersActionsUI.clickCreateAlertButton(); - await ml.alerting.setRuleName('test-ecommerce'); await ml.alerting.selectAnomalyDetectionAlertType(); + await pageObjects.header.waitUntilLoadingHasFinished(); + await ml.alerting.setRuleName('test-ecommerce'); await ml.testExecution.logTestStep('should have correct default values'); await ml.alerting.assertSeverity(75); await ml.alerting.assertPreviewButtonState(false); diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts index 10392e3c45f88..2fa64a1d1bfff 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/es_query_rule.ts @@ -37,8 +37,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.header.waitUntilLoadingHasFinished(); await rules.common.clickCreateAlertButton(); - await testSubjects.setValue('ruleNameInput', ruleName); await testSubjects.click(`.es-query-SelectOption`); + await pageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.setValue('ruleNameInput', ruleName); await testSubjects.click('queryFormType_esQuery'); const indexSelector = await testSubjects.find('selectIndexExpression'); await indexSelector.click(); diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts index 582bc02bd5609..e06558e281a7b 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index_threshold_rule.ts @@ -23,27 +23,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.header.waitUntilLoadingHasFinished(); await rules.common.clickCreateAlertButton(); - await testSubjects.setValue('ruleNameInput', ruleName); - await testSubjects.click('tagsComboBox'); - await testSubjects.setValue('tagsComboBox', 'sample-data'); - await testSubjects.click('solutionsFilterButton'); - await testSubjects.click('solutionstackAlertsFilterOption'); - await testSubjects.setValue('solutionsFilterButton', 'solutionstackAlertsFilterOption'); - await commonScreenshots.takeScreenshot( - 'rule-types-index-threshold-select', - screenshotDirectories, - 1400, - 1024 - ); - await testSubjects.click('.index-threshold-SelectOption'); + await pageObjects.header.waitUntilLoadingHasFinished(); await commonScreenshots.takeScreenshot( 'rule-types-index-threshold-conditions', screenshotDirectories, 1400, 1300 ); - + await testSubjects.setValue('ruleNameInput', ruleName); + await testSubjects.click('tagsComboBox'); + await testSubjects.setValue('tagsComboBox', 'sample-data'); await testSubjects.scrollIntoView('selectIndexExpression'); await testSubjects.click('selectIndexExpression'); await comboBox.set('thresholdIndexesComboBox', 'kibana_sample_data_logs '); diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/tracking_containment_rule.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/tracking_containment_rule.ts index 13de6e45a88d4..826c95cad10f7 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/tracking_containment_rule.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/tracking_containment_rule.ts @@ -20,10 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.header.waitUntilLoadingHasFinished(); await rules.common.clickCreateAlertButton(); - await testSubjects.click('solutionsFilterButton'); - await testSubjects.click('solutionstackAlertsFilterOption'); - await testSubjects.setValue('solutionsFilterButton', 'solutionstackAlertsFilterOption'); await testSubjects.click('.geo-containment-SelectOption'); + await pageObjects.header.waitUntilLoadingHasFinished(); await comboBox.setCustom('entitiesDataView', 'Kibana Sample Data Logs'); await commonScreenshots.takeScreenshot( 'alert-types-tracking-containment-conditions', diff --git a/x-pack/test/screenshot_creation/apps/transform_docs/transform_alerts.ts b/x-pack/test/screenshot_creation/apps/transform_docs/transform_alerts.ts index 0b6b70a6e174b..26ef92a2fc739 100644 --- a/x-pack/test/screenshot_creation/apps/transform_docs/transform_alerts.ts +++ b/x-pack/test/screenshot_creation/apps/transform_docs/transform_alerts.ts @@ -14,7 +14,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const transform = getService('transform'); const screenshotDirectories = ['transform_docs']; - const pageObjects = getPageObjects(['triggersActionsUI']); + const pageObjects = getPageObjects(['triggersActionsUI', 'header']); let testTransformId = ''; @@ -37,20 +37,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await transform.testExecution.logTestStep('navigate to stack management rules'); await transform.navigation.navigateToRules(); await pageObjects.triggersActionsUI.clickCreateAlertButton(); - await transform.alerting.setRuleName('transform-health-rule'); - - await transform.testExecution.logTestStep( - 'search for transform rule type and take screenshot' - ); - const searchBox = await testSubjects.find('ruleSearchField'); - await searchBox.click(); - await searchBox.clearValue(); - await searchBox.type('transform'); - await searchBox.pressKeys(browser.keys.ENTER); - await commonScreenshots.takeScreenshot('transform-rule', screenshotDirectories); - + await pageObjects.header.waitUntilLoadingHasFinished(); await transform.testExecution.logTestStep('select transform details and take screenshot'); await transform.alerting.selectTransformAlertType(); + await transform.alerting.setRuleName('transform-health-rule'); testTransformId = '*'; await transform.alerting.selectTransforms([testTransformId]); await commonScreenshots.takeScreenshot('transform-check-config', screenshotDirectories); diff --git a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts index ae1004db12cf3..7fdea1ff3d648 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts @@ -35,7 +35,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/discover/default' ); await kibanaServer.uiSettings.replace({ - 'discover:enableESQL': true, + enableESQL: true, }); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts index 7fd34aff67690..c629f5056ef3a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts @@ -66,7 +66,7 @@ export default ({ getService }: FtrProviderContext) => { const interval: [string, string] = ['2020-10-28T06:00:00.000Z', '2020-10-28T06:10:00.000Z']; const doc1 = { agent: { name: 'test-1' } }; const doc2 = { agent: { name: 'test-2' } }; - const ruleQuery = `from ecs_compliant [metadata _id, _index, _version] ${internalIdPipe( + const ruleQuery = `from ecs_compliant metadata _id, _index, _version ${internalIdPipe( id )} | where agent.name=="test-1"`; const rule: EsqlRuleCreateProps = { @@ -243,7 +243,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock('rule-1', true), // only _id and agent.name is projected at the end of query pipeline - query: `from ecs_compliant [metadata _id] ${internalIdPipe(id)} | keep _id, agent.name`, + query: `from ecs_compliant metadata _id ${internalIdPipe(id)} | keep _id, agent.name`, from: 'now-1h', interval: '1h', }; @@ -278,6 +278,44 @@ export default ({ getService }: FtrProviderContext) => { ); }); + it('should support deprecated [metadata _id] syntax', async () => { + const id = uuidv4(); + const interval: [string, string] = ['2020-10-28T06:00:00.000Z', '2020-10-28T06:10:00.000Z']; + const doc1 = { + agent: { name: 'test-1', version: '2', type: 'auditbeat' }, + host: { name: 'my-host' }, + client: { ip: '127.0.0.1' }, + }; + + const rule: EsqlRuleCreateProps = { + ...getCreateEsqlRulesSchemaMock('rule-1', true), + // only _id and agent.name is projected at the end of query pipeline + query: `from ecs_compliant [metadata _id] ${internalIdPipe(id)} | keep _id, agent.name`, + from: 'now-1h', + interval: '1h', + }; + + await indexEnhancedDocuments({ + documents: [doc1], + interval, + id, + }); + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + }); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 10, + }); + + expect(previewAlerts.length).toBe(1); + }); + it('should deduplicate alerts correctly based on source document _id', async () => { const id = uuidv4(); // document will fall into 2 rule execution windows @@ -290,7 +328,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock('rule-1', true), // only _id and agent.name is projected at the end of query pipeline - query: `from ecs_compliant [metadata _id] ${internalIdPipe(id)} | keep _id, agent.name`, + query: `from ecs_compliant metadata _id ${internalIdPipe(id)} | keep _id, agent.name`, from: 'now-45m', interval: '30m', }; @@ -725,7 +763,7 @@ export default ({ getService }: FtrProviderContext) => { const id = uuidv4(); const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock(`rule-${id}`, true), - query: `from ecs_compliant [metadata _id] ${internalIdPipe( + query: `from ecs_compliant metadata _id ${internalIdPipe( id )} | keep _id, agent.name | sort agent.name`, from: '2020-10-28T05:15:00.000Z', @@ -913,7 +951,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock('rule-1', true), - query: `from ecs_compliant [metadata _id] ${internalIdPipe( + query: `from ecs_compliant metadata _id ${internalIdPipe( id )} | where agent.name=="test-1"`, from: 'now-1h', @@ -956,7 +994,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock('rule-1', true), - query: `from ecs_compliant [metadata _id] ${internalIdPipe( + query: `from ecs_compliant metadata _id ${internalIdPipe( id )} | where agent.name=="test-1"`, from: 'now-1h', @@ -1021,7 +1059,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: EsqlRuleCreateProps = { ...getCreateEsqlRulesSchemaMock('rule-1', true), - query: `from ecs_non_compliant [metadata _id] ${internalIdPipe(id)}`, + query: `from ecs_non_compliant metadata _id ${internalIdPipe(id)}`, from: 'now-1h', interval: '1h', }; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts index 945cf43967cbe..2e95bb19a0477 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule_ess.cy.ts @@ -138,7 +138,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { cy.get(ESQL_QUERY_BAR).should('not.be.visible'); }); - it('shows error when non-aggregating ES|QL query does not [metadata] operator', function () { + it('shows error when non-aggregating ES|QL query does not have metadata operator', function () { workaroundForResizeObserver(); const invalidNonAggregatingQuery = 'from auditbeat* | limit 5'; @@ -148,7 +148,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains( - 'must include the [metadata _id, _version, _index] operator after the source command' + 'must include the "metadata _id, _version, _index" operator after the source command' ); }); @@ -156,7 +156,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { workaroundForResizeObserver(); const invalidNonAggregatingQuery = - 'from auditbeat* [metadata _id, _version, _index] | keep agent.* | limit 5'; + 'from auditbeat* metadata _id, _version, _index | keep agent.* | limit 5'; selectEsqlRuleType(); expandEsqlQueryBar(); @@ -164,14 +164,14 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains( - 'must include the [metadata _id, _version, _index] operator after the source command' + 'must include the "metadata _id, _version, _index" operator after the source command' ); }); it('shows error when ES|QL query is invalid', function () { workaroundForResizeObserver(); const invalidEsqlQuery = - 'from auditbeat* [metadata _id, _version, _index] | not_existing_operator'; + 'from auditbeat* metadata _id, _version, _index | not_existing_operator'; visit(CREATE_RULE_URL); selectEsqlRuleType(); @@ -191,7 +191,7 @@ describe('Detection ES|QL rules, creation', { tags: ['@ess'] }, () => { it('shows custom ES|QL field in investigation fields autocomplete and saves it in rule', function () { const CUSTOM_ESQL_FIELD = '_custom_agent_name'; const queryWithCustomFields = [ - `from auditbeat* [metadata _id, _version, _index]`, + `from auditbeat* metadata _id, _version, _index`, `eval ${CUSTOM_ESQL_FIELD} = agent.name`, `keep _id, _custom_agent_name`, `limit 5`, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/value_lists/value_list_items.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/value_lists/value_list_items.cy.ts index 2f8d6afb8c836..43d84c2f45315 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/value_lists/value_list_items.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/value_lists/value_list_items.cy.ts @@ -43,16 +43,7 @@ import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; describe( 'Value list items', { - tags: ['@ess', '@serverless', '@skipInServerlessMKI'], - env: { - ftrConfig: { - kbnServerArgs: [ - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'valueListItemsModalEnabled', - ])}`, - ], - }, - }, + tags: ['@ess', '@serverless'], }, () => { beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts index bb5d45654e287..fe8a63efb1102 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts @@ -40,7 +40,8 @@ import { visit } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; // Iusse tracked in: https://github.com/elastic/kibana/issues/167809 -describe('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/182206 +describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts index 17e1e6458edaa..8069cdd98bb22 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts @@ -47,7 +47,8 @@ export default function (providerContext: FtrProviderContext) { let agentPolicyId: string; - describe('STATUS = INDEXING TEST', () => { + // FLAKY: https://github.com/elastic/kibana/issues/181777 + describe.skip('STATUS = INDEXING TEST', () => { beforeEach(async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); diff --git a/yarn.lock b/yarn.lock index b457726208cab..f2762ca8f76e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15004,10 +15004,10 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.5.2.tgz#7c4a9d77a891302f16e988a6ff4ade5563e805ec" - integrity sha512-m4hvI/1MpVlR5B8k6BOU7WzdFOXjYux/iRso6av6qqJhYKSbHKhF93ncatw4nXU1q88tu31gwB9CzA2/6F4hFQ== +elastic-apm-node@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.5.3.tgz#8015f1525f6cd45daef6a48a88d93dbaf5899212" + integrity sha512-FE+srHVTtvDp/SfY76yxSIupCU8Z30WuOx1Yf3plUXeyLuKqq9YRk6uX0Jleb1jfwG58lvabpeU0u9YmS1YdrA== dependencies: "@elastic/ecs-pino-format" "^1.5.0" "@opentelemetry/api" "^1.4.1" @@ -15193,11 +15193,6 @@ entities@^4.2.0, entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -20563,13 +20558,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -linkify-it@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" - integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== - dependencies: - uc.micro "^1.0.1" - linkify-it@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" @@ -21189,28 +21177,17 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.1.tgz#1994df2d3af4811de59a6714934c2b2292734518" integrity sha1-GZTfLTr0gR3lmmcUk0wrIpJzRRg= -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -markdown-it@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.0.0.tgz#b4b2ddeb0f925e88d981f84c183b59bac9e3741b" - integrity sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw== +markdown-it@^14.0.0, markdown-it@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" + integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== dependencies: argparse "^2.0.1" entities "^4.4.0" linkify-it "^5.0.0" mdurl "^2.0.0" punycode.js "^2.3.1" - uc.micro "^2.0.0" + uc.micro "^2.1.0" markdown-table@^2.0.0: version "2.0.0" @@ -21384,7 +21361,7 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== -mdurl@^1.0.0, mdurl@^1.0.1: +mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= @@ -29243,15 +29220,10 @@ typewise@^1.0.3: dependencies: typewise-core "^1.2.0" -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" - integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== - -uc.micro@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.0.0.tgz#84b3c335c12b1497fd9e80fcd3bfa7634c363ff1" - integrity sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== uglify-js@^3.1.4: version "3.17.4"