From 2ee5e66b260212292f3792b91cbfd4324015bdf4 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 1 Sep 2021 21:18:23 -0400 Subject: [PATCH] [7.x] [eslint] prevent async Promise constructor mistakes (#110349) (#110727) * [eslint] prevent async Promise constructor mistakes (#110349) Co-authored-by: spalger * autofix one more violation Co-authored-by: Spencer Co-authored-by: spalger --- package.json | 2 + .../elastic-eslint-config-kibana/.eslintrc.js | 2 + packages/kbn-eslint-plugin-eslint/index.js | 1 + .../rules/no_async_promise_body.js | 165 ++++++++ .../rules/no_async_promise_body.test.js | 254 ++++++++++++ .../application/integration_tests/utils.tsx | 17 +- .../application/ui/app_container.test.tsx | 8 +- .../ui/header/header_action_menu.test.tsx | 17 +- .../active_cursor/use_active_cursor.test.ts | 75 ++-- .../public/util/mount_point_portal.test.tsx | 17 +- .../lazy_load_bundle/get_service_settings.ts | 12 +- .../public/lazy_load_bundle/index.ts | 17 +- .../public/top_nav_menu/top_nav_menu.test.tsx | 17 +- .../public/legacy/vis_controller.ts | 57 +-- .../vislib/components/legend/legend.tsx | 27 +- .../public/lazy_load_bundle/index.ts | 14 +- .../public/lazy_load_bundle/index.ts | 21 +- .../mappings_editor/use_state_listener.tsx | 20 +- .../maps/public/lazy_load_bundle/index.ts | 63 +-- .../load_new_job_capabilities.ts | 45 ++- .../anomaly_charts_setup_flyout.tsx | 57 +-- .../anomaly_swimlane_setup_flyout.tsx | 57 +-- .../common/resolve_job_selection.tsx | 107 ++--- .../lib/logstash/get_paginated_pipelines.js | 101 +++-- .../session_management/session_index.ts | 110 ++--- .../routes/rules/import_rules_route.ts | 379 +++++++++--------- .../timelines/import_timelines/helpers.ts | 203 +++++----- .../configuration_statistics.test.ts | 106 ++--- .../monitoring/workload_statistics.test.ts | 57 ++- .../task_manager/server/task_scheduling.ts | 84 ++-- .../services/app_search_client.ts | 20 +- x-pack/test/lists_api_integration/utils.ts | 36 +- yarn.lock | 38 ++ 33 files changed, 1420 insertions(+), 786 deletions(-) create mode 100644 packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js create mode 100644 packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js diff --git a/package.json b/package.json index 4824bcbb9107..79a2e66ed91b 100644 --- a/package.json +++ b/package.json @@ -657,6 +657,7 @@ "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/typescript-estree": "^4.14.1", "@typescript-eslint/parser": "^4.14.1", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", @@ -727,6 +728,7 @@ "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-perf": "^3.2.3", + "eslint-traverse": "^1.0.0", "expose-loader": "^0.7.5", "faker": "^5.1.0", "fancy-log": "^1.3.2", diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js index 08344983d6d7..1c517b2421b8 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -90,5 +90,7 @@ module.exports = { }, ], ], + + '@kbn/eslint/no_async_promise_body': 'error', }, }; diff --git a/packages/kbn-eslint-plugin-eslint/index.js b/packages/kbn-eslint-plugin-eslint/index.js index e5a38e5f0952..a7a9c6b5bebd 100644 --- a/packages/kbn-eslint-plugin-eslint/index.js +++ b/packages/kbn-eslint-plugin-eslint/index.js @@ -12,5 +12,6 @@ module.exports = { 'disallow-license-headers': require('./rules/disallow_license_headers'), 'no-restricted-paths': require('./rules/no_restricted_paths'), module_migration: require('./rules/module_migration'), + no_async_promise_body: require('./rules/no_async_promise_body'), }, }; diff --git a/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js new file mode 100644 index 000000000000..317758fd3629 --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.js @@ -0,0 +1,165 @@ +/* + * 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. + */ + +const { parseExpression } = require('@babel/parser'); +const { default: generate } = require('@babel/generator'); +const tsEstree = require('@typescript-eslint/typescript-estree'); +const traverse = require('eslint-traverse'); +const esTypes = tsEstree.AST_NODE_TYPES; +const babelTypes = require('@babel/types'); + +/** @typedef {import("eslint").Rule.RuleModule} Rule */ +/** @typedef {import("@typescript-eslint/parser").ParserServices} ParserServices */ +/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Expression} Expression */ +/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.ArrowFunctionExpression} ArrowFunctionExpression */ +/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.FunctionExpression} FunctionExpression */ +/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.TryStatement} TryStatement */ +/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.NewExpression} NewExpression */ +/** @typedef {import("typescript").ExportDeclaration} ExportDeclaration */ +/** @typedef {import("eslint").Rule.RuleFixer} Fixer */ + +const ERROR_MSG = + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections'; + +/** + * @param {Expression} node + */ +const isPromise = (node) => node.type === esTypes.Identifier && node.name === 'Promise'; + +/** + * @param {Expression} node + * @returns {node is ArrowFunctionExpression | FunctionExpression} + */ +const isFunc = (node) => + node.type === esTypes.ArrowFunctionExpression || node.type === esTypes.FunctionExpression; + +/** + * @param {any} context + * @param {ArrowFunctionExpression | FunctionExpression} node + */ +const isFuncBodySafe = (context, node) => { + // if the body isn't wrapped in a blockStatement it can't have a try/catch at the root + if (node.body.type !== esTypes.BlockStatement) { + return false; + } + + // when the entire body is wrapped in a try/catch it is the only node + if (node.body.body.length !== 1) { + return false; + } + + const tryNode = node.body.body[0]; + // ensure we have a try node with a handler + if (tryNode.type !== esTypes.TryStatement || !tryNode.handler) { + return false; + } + + // ensure the handler doesn't throw + let hasThrow = false; + traverse(context, tryNode.handler, (path) => { + if (path.node.type === esTypes.ThrowStatement) { + hasThrow = true; + return traverse.STOP; + } + }); + return !hasThrow; +}; + +/** + * @param {string} code + */ +const wrapFunctionInTryCatch = (code) => { + // parse the code with babel so we can mutate the AST + const ast = parseExpression(code, { + plugins: ['typescript', 'jsx'], + }); + + // validate that the code reperesents an arrow or function expression + if (!babelTypes.isArrowFunctionExpression(ast) && !babelTypes.isFunctionExpression(ast)) { + throw new Error('expected function to be an arrow or function expression'); + } + + // ensure that the function receives the second argument, and capture its name if already defined + let rejectName = 'reject'; + if (ast.params.length === 0) { + ast.params.push(babelTypes.identifier('resolve'), babelTypes.identifier(rejectName)); + } else if (ast.params.length === 1) { + ast.params.push(babelTypes.identifier(rejectName)); + } else if (ast.params.length === 2) { + if (babelTypes.isIdentifier(ast.params[1])) { + rejectName = ast.params[1].name; + } else { + throw new Error('expected second param of promise definition function to be an identifier'); + } + } + + // ensure that the body of the function is a blockStatement + let block = ast.body; + if (!babelTypes.isBlockStatement(block)) { + block = babelTypes.blockStatement([babelTypes.returnStatement(block)]); + } + + // redefine the body of the function as a new blockStatement containing a tryStatement + // which catches errors and forwards them to reject() when caught + ast.body = babelTypes.blockStatement([ + // try { + babelTypes.tryStatement( + block, + // catch (error) { + babelTypes.catchClause( + babelTypes.identifier('error'), + babelTypes.blockStatement([ + // reject(error) + babelTypes.expressionStatement( + babelTypes.callExpression(babelTypes.identifier(rejectName), [ + babelTypes.identifier('error'), + ]) + ), + ]) + ) + ), + ]); + + return generate(ast).code; +}; + +/** @type {Rule} */ +module.exports = { + meta: { + fixable: 'code', + schema: [], + }, + create: (context) => ({ + NewExpression(_) { + const node = /** @type {NewExpression} */ (_); + + // ensure we are newing up a promise with a single argument + if (!isPromise(node.callee) || node.arguments.length !== 1) { + return; + } + + const func = node.arguments[0]; + // ensure the argument is an arrow or function expression and is async + if (!isFunc(func) || !func.async) { + return; + } + + // body must be a blockStatement, try/catch can't exist outside of a block + if (!isFuncBodySafe(context, func)) { + context.report({ + message: ERROR_MSG, + loc: func.loc, + fix(fixer) { + const source = context.getSourceCode(); + return fixer.replaceText(func, wrapFunctionInTryCatch(source.getText(func))); + }, + }); + } + }, + }), +}; diff --git a/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js new file mode 100644 index 000000000000..f5929b1b3966 --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/no_async_promise_body.test.js @@ -0,0 +1,254 @@ +/* + * 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. + */ + +const { RuleTester } = require('eslint'); +const rule = require('./no_async_promise_body'); +const dedent = require('dedent'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + }, + }, +}); + +ruleTester.run('@kbn/eslint/no_async_promise_body', rule, { + valid: [ + // caught but no resolve + { + code: dedent` + new Promise(async function (resolve) { + try { + await asyncOperation(); + } catch (error) { + // noop + } + }) + `, + }, + // arrow caught but no resolve + { + code: dedent` + new Promise(async (resolve) => { + try { + await asyncOperation(); + } catch (error) { + // noop + } + }) + `, + }, + // caught with reject + { + code: dedent` + new Promise(async function (resolve, reject) { + try { + await asyncOperation(); + } catch (error) { + reject(error) + } + }) + `, + }, + // arrow caught with reject + { + code: dedent` + new Promise(async (resolve, reject) => { + try { + await asyncOperation(); + } catch (error) { + reject(error) + } + }) + `, + }, + // non async + { + code: dedent` + new Promise(function (resolve) { + setTimeout(resolve, 10); + }) + `, + }, + // arrow non async + { + code: dedent` + new Promise((resolve) => setTimeout(resolve, 10)) + `, + }, + ], + + invalid: [ + // no catch + { + code: dedent` + new Promise(async function (resolve) { + const result = await asyncOperation(); + resolve(result); + }) + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async function (resolve, reject) { + try { + const result = await asyncOperation(); + resolve(result); + } catch (error) { + reject(error); + } + }) + `, + }, + // arrow no catch + { + code: dedent` + new Promise(async (resolve) => { + const result = await asyncOperation(); + resolve(result); + }) + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async (resolve, reject) => { + try { + const result = await asyncOperation(); + resolve(result); + } catch (error) { + reject(error); + } + }) + `, + }, + // catch, but it throws + { + code: dedent` + new Promise(async function (resolve) { + try { + const result = await asyncOperation(); + resolve(result); + } catch (error) { + if (error.code === 'foo') { + throw error; + } + } + }) + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async function (resolve, reject) { + try { + try { + const result = await asyncOperation(); + resolve(result); + } catch (error) { + if (error.code === 'foo') { + throw error; + } + } + } catch (error) { + reject(error); + } + }) + `, + }, + // no catch without block + { + code: dedent` + new Promise(async (resolve) => resolve(await asyncOperation())); + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async (resolve, reject) => { + try { + return resolve(await asyncOperation()); + } catch (error) { + reject(error); + } + }); + `, + }, + // no catch with named reject + { + code: dedent` + new Promise(async (resolve, rej) => { + const result = await asyncOperation(); + result ? resolve(true) : rej() + }); + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async (resolve, rej) => { + try { + const result = await asyncOperation(); + result ? resolve(true) : rej(); + } catch (error) { + rej(error); + } + }); + `, + }, + // no catch with no args + { + code: dedent` + new Promise(async () => { + await asyncOperation(); + }); + `, + errors: [ + { + line: 1, + message: + 'Passing an async function to the Promise constructor leads to a hidden promise being created and prevents handling rejections', + }, + ], + output: dedent` + new Promise(async (resolve, reject) => { + try { + await asyncOperation(); + } catch (error) { + reject(error); + } + }); + `, + }, + ], +}); diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index dcf071719c11..455d19956f7e 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -21,13 +21,18 @@ export const createRenderer = (element: ReactElement | null): Renderer => { const dom: Dom = element && mount({element}); return () => - new Promise(async (resolve) => { - if (dom) { - await act(async () => { - dom.update(); - }); + new Promise(async (resolve, reject) => { + try { + if (dom) { + await act(async () => { + dom.update(); + }); + } + + setImmediate(() => resolve(dom)); // flushes any pending promises + } catch (error) { + reject(error); } - setImmediate(() => resolve(dom)); // flushes any pending promises }); }; diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index 86cb9198e069..4c056e748f06 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -27,8 +27,12 @@ describe('AppContainer', () => { }); const flushPromises = async () => { - await new Promise(async (resolve) => { - setImmediate(() => resolve()); + await new Promise(async (resolve, reject) => { + try { + setImmediate(() => resolve()); + } catch (error) { + reject(error); + } }); }; diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx index 386e48e745e8..201be8848bac 100644 --- a/src/core/public/chrome/ui/header/header_action_menu.test.tsx +++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx @@ -26,13 +26,18 @@ describe('HeaderActionMenu', () => { }); const refresh = () => { - new Promise(async (resolve) => { - if (component) { - act(() => { - component.update(); - }); + new Promise(async (resolve, reject) => { + try { + if (component) { + act(() => { + component.update(); + }); + } + + setImmediate(() => resolve(component)); // flushes any pending promises + } catch (error) { + reject(error); } - setImmediate(() => resolve(component)); // flushes any pending promises }); }; diff --git a/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts b/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts index da66ef05baff..50e7c995a125 100644 --- a/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts +++ b/src/plugins/charts/public/services/active_cursor/use_active_cursor.test.ts @@ -25,42 +25,47 @@ describe.skip('useActiveCursor', () => { events: Array>, eventsTimeout = 1 ) => - new Promise(async (resolve) => { - const activeCursor = new ActiveCursor(); - let allEventsExecuted = false; - - activeCursor.setup(); - - dispatchExternalPointerEvent.mockImplementation((pointerEvent) => { - if (allEventsExecuted) { - resolve(pointerEvent); - } - }); - - renderHook(() => - useActiveCursor( - activeCursor, - { - current: { - dispatchExternalPointerEvent: dispatchExternalPointerEvent as ( - pointerEvent: PointerEvent - ) => void, - }, - } as RefObject, - { ...syncOption, debounce: syncOption.debounce ?? 1 } - ) - ); - - for (const e of events) { - await new Promise((eventResolve) => - setTimeout(() => { - if (e === events[events.length - 1]) { - allEventsExecuted = true; - } - activeCursor.activeCursor$!.next({ cursor, ...e }); - eventResolve(null); - }, eventsTimeout) + new Promise(async (resolve, reject) => { + try { + const activeCursor = new ActiveCursor(); + let allEventsExecuted = false; + activeCursor.setup(); + dispatchExternalPointerEvent.mockImplementation((pointerEvent) => { + if (allEventsExecuted) { + resolve(pointerEvent); + } + }); + renderHook(() => + useActiveCursor( + activeCursor, + { + current: { + dispatchExternalPointerEvent: dispatchExternalPointerEvent as ( + pointerEvent: PointerEvent + ) => void, + }, + } as RefObject, + { ...syncOption, debounce: syncOption.debounce ?? 1 } + ) ); + + for (const e of events) { + await new Promise((eventResolve) => + setTimeout(() => { + if (e === events[events.length - 1]) { + allEventsExecuted = true; + } + + activeCursor.activeCursor$!.next({ + cursor, + ...e, + }); + eventResolve(null); + }, eventsTimeout) + ); + } + } catch (error) { + reject(error); } }); diff --git a/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx index 53503b197567..39e345568a29 100644 --- a/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx +++ b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx @@ -19,13 +19,18 @@ describe('MountPointPortal', () => { let dom: ReactWrapper; const refresh = () => { - new Promise(async (resolve) => { - if (dom) { - act(() => { - dom.update(); - }); + new Promise(async (resolve, reject) => { + try { + if (dom) { + act(() => { + dom.update(); + }); + } + + setImmediate(() => resolve(dom)); // flushes any pending promises + } catch (error) { + reject(error); } - setImmediate(() => resolve(dom)); // flushes any pending promises }); }; diff --git a/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts b/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts index 6e32ff5d4e41..8eafada176e7 100644 --- a/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts +++ b/src/plugins/maps_ems/public/lazy_load_bundle/get_service_settings.ts @@ -16,10 +16,14 @@ export async function getServiceSettings(): Promise { return loadPromise; } - loadPromise = new Promise(async (resolve) => { - const { ServiceSettings } = await import('./lazy'); - const config = getMapsEmsConfig(); - resolve(new ServiceSettings(config, config.tilemap)); + loadPromise = new Promise(async (resolve, reject) => { + try { + const { ServiceSettings } = await import('./lazy'); + const config = getMapsEmsConfig(); + resolve(new ServiceSettings(config, config.tilemap)); + } catch (error) { + reject(error); + } }); return loadPromise; } diff --git a/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts b/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts index b1509c4effa7..0017849408ef 100644 --- a/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts +++ b/src/plugins/maps_legacy/public/lazy_load_bundle/index.ts @@ -18,13 +18,16 @@ export async function lazyLoadMapsLegacyModules(): Promise { - const { KibanaMap, L } = await import('./lazy'); - - resolve({ - KibanaMap, - L, - }); + loadModulesPromise = new Promise(async (resolve, reject) => { + try { + const { KibanaMap, L } = await import('./lazy'); + resolve({ + KibanaMap, + L, + }); + } catch (error) { + reject(error); + } }); return loadModulesPromise; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 558739a44dd4..45b9b4c7a885 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -109,13 +109,18 @@ describe('TopNavMenu', () => { let dom: ReactWrapper; const refresh = () => { - new Promise(async (resolve) => { - if (dom) { - act(() => { - dom.update(); - }); + new Promise(async (resolve, reject) => { + try { + if (dom) { + act(() => { + dom.update(); + }); + } + + setImmediate(() => resolve(dom)); // flushes any pending promises + } catch (error) { + reject(error); } - setImmediate(() => resolve(dom)); // flushes any pending promises }); }; diff --git a/src/plugins/vis_type_table/public/legacy/vis_controller.ts b/src/plugins/vis_type_table/public/legacy/vis_controller.ts index ec198aa96f1f..a9cb22a05691 100644 --- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts +++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts @@ -71,35 +71,42 @@ export function getTableVisualizationControllerClass( await this.initLocalAngular(); return new Promise(async (resolve, reject) => { - if (!this.$rootScope) { - const $injector = this.getInjector(); - this.$rootScope = $injector.get('$rootScope'); - this.$compile = $injector.get('$compile'); - } - const updateScope = () => { - if (!this.$scope) { - return; + try { + if (!this.$rootScope) { + const $injector = this.getInjector(); + this.$rootScope = $injector.get('$rootScope'); + this.$compile = $injector.get('$compile'); } - this.$scope.visState = { params: visParams, title: visParams.title }; - this.$scope.esResponse = esResponse; + const updateScope = () => { + if (!this.$scope) { + return; + } - this.$scope.visParams = visParams; - this.$scope.renderComplete = resolve; - this.$scope.renderFailed = reject; - this.$scope.resize = Date.now(); - this.$scope.$apply(); - }; + this.$scope.visState = { + params: visParams, + title: visParams.title, + }; + this.$scope.esResponse = esResponse; + this.$scope.visParams = visParams; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + this.$scope.resize = Date.now(); + this.$scope.$apply(); + }; - if (!this.$scope && this.$compile) { - this.$scope = this.$rootScope.$new(); - this.$scope.uiState = handlers.uiState; - this.$scope.filter = handlers.event; - updateScope(); - this.el.find('div').append(this.$compile(tableVisTemplate)(this.$scope)); - this.$scope.$apply(); - } else { - updateScope(); + if (!this.$scope && this.$compile) { + this.$scope = this.$rootScope.$new(); + this.$scope.uiState = handlers.uiState; + this.$scope.filter = handlers.event; + updateScope(); + this.el.find('div').append(this.$compile(tableVisTemplate)(this.$scope)); + this.$scope.$apply(); + } else { + updateScope(); + } + } catch (error) { + reject(error); } }); } diff --git a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx index 56f9025a6bd0..4701d07ab83e 100644 --- a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx +++ b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx @@ -124,16 +124,25 @@ export class VisLegend extends PureComponent { }; setFilterableLabels = (items: LegendItem[]): Promise => - new Promise(async (resolve) => { - const filterableLabels = new Set(); - items.forEach(async (item) => { - const canFilter = await this.canFilter(item); - if (canFilter) { - filterableLabels.add(item.label); - } - }); + new Promise(async (resolve, reject) => { + try { + const filterableLabels = new Set(); + items.forEach(async (item) => { + const canFilter = await this.canFilter(item); - this.setState({ filterableLabels }, resolve); + if (canFilter) { + filterableLabels.add(item.label); + } + }); + this.setState( + { + filterableLabels, + }, + resolve + ); + } catch (error) { + reject(error); + } }); setLabels = (data: any, type: string) => { diff --git a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts index 57f0872d6258..f04c611c2fae 100644 --- a/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/data_visualizer/public/lazy_load_bundle/index.ts @@ -22,13 +22,13 @@ export async function lazyLoadModules(): Promise { return loadModulesPromise; } - loadModulesPromise = new Promise(async (resolve) => { - const lazyImports = await import('./lazy'); - - resolve({ - ...lazyImports, - getHttp: () => getCoreStart().http, - }); + loadModulesPromise = new Promise(async (resolve, reject) => { + try { + const lazyImports = await import('./lazy'); + resolve({ ...lazyImports, getHttp: () => getCoreStart().http }); + } catch (error) { + reject(error); + } }); return loadModulesPromise; } diff --git a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts index 9c7c6ff1e518..192a7ffb5e78 100644 --- a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts @@ -44,15 +44,18 @@ export async function lazyLoadModules(): Promise { return loadModulesPromise; } - loadModulesPromise = new Promise(async (resolve) => { - const { JsonUploadAndParse, importerFactory, IndexNameForm } = await import('./lazy'); - - resolve({ - JsonUploadAndParse, - importerFactory, - getHttp, - IndexNameForm, - }); + loadModulesPromise = new Promise(async (resolve, reject) => { + try { + const { JsonUploadAndParse, importerFactory, IndexNameForm } = await import('./lazy'); + resolve({ + JsonUploadAndParse, + importerFactory, + getHttp, + IndexNameForm, + }); + } catch (error) { + reject(error); + } }); return loadModulesPromise; } diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx index 540da3e2b9fd..cf1f9e1bc39e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx @@ -107,17 +107,25 @@ export const useMappingsStateListener = ({ onChange, value, mappingsType }: Args validate: async () => { const configurationFormValidator = state.configuration.submitForm !== undefined - ? new Promise(async (resolve) => { - const { isValid } = await state.configuration.submitForm!(); - resolve(isValid); + ? new Promise(async (resolve, reject) => { + try { + const { isValid } = await state.configuration.submitForm!(); + resolve(isValid); + } catch (error) { + reject(error); + } }) : Promise.resolve(true); const templatesFormValidator = state.templates.submitForm !== undefined - ? new Promise(async (resolve) => { - const { isValid } = await state.templates.submitForm!(); - resolve(isValid); + ? new Promise(async (resolve, reject) => { + try { + const { isValid } = await state.templates.submitForm!(); + resolve(isValid); + } catch (error) { + reject(error); + } }) : Promise.resolve(true); diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index abc333ab5e06..98e3112be4a6 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -83,36 +83,39 @@ export async function lazyLoadMapModules(): Promise { return loadModulesPromise; } - loadModulesPromise = new Promise(async (resolve) => { - const { - MapEmbeddable, - getIndexPatternService, - getMapsCapabilities, - renderApp, - createSecurityLayerDescriptors, - registerLayerWizard, - registerSource, - createTileMapLayerDescriptor, - createRegionMapLayerDescriptor, - createBasemapLayerDescriptor, - createESSearchSourceLayerDescriptor, - suggestEMSTermJoinConfig, - } = await import('./lazy'); - - resolve({ - MapEmbeddable, - getIndexPatternService, - getMapsCapabilities, - renderApp, - createSecurityLayerDescriptors, - registerLayerWizard, - registerSource, - createTileMapLayerDescriptor, - createRegionMapLayerDescriptor, - createBasemapLayerDescriptor, - createESSearchSourceLayerDescriptor, - suggestEMSTermJoinConfig, - }); + loadModulesPromise = new Promise(async (resolve, reject) => { + try { + const { + MapEmbeddable, + getIndexPatternService, + getMapsCapabilities, + renderApp, + createSecurityLayerDescriptors, + registerLayerWizard, + registerSource, + createTileMapLayerDescriptor, + createRegionMapLayerDescriptor, + createBasemapLayerDescriptor, + createESSearchSourceLayerDescriptor, + suggestEMSTermJoinConfig, + } = await import('./lazy'); + resolve({ + MapEmbeddable, + getIndexPatternService, + getMapsCapabilities, + renderApp, + createSecurityLayerDescriptors, + registerLayerWizard, + registerSource, + createTileMapLayerDescriptor, + createRegionMapLayerDescriptor, + createBasemapLayerDescriptor, + createESSearchSourceLayerDescriptor, + suggestEMSTermJoinConfig, + }); + } catch (error) { + reject(error); + } }); return loadModulesPromise; } diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts index a99834353524..d3b407c2bb65 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts @@ -23,27 +23,34 @@ export function loadNewJobCapabilities( jobType: JobType ) { return new Promise(async (resolve, reject) => { - const serviceToUse = - jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics; - if (indexPatternId !== undefined) { - // index pattern is being used - const indexPattern: IIndexPattern = await indexPatterns.get(indexPatternId); - await serviceToUse.initializeFromIndexPattern(indexPattern); - resolve(serviceToUse.newJobCaps); - } else if (savedSearchId !== undefined) { - // saved search is being used - // load the index pattern from the saved search - const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId); - if (indexPattern === null) { - // eslint-disable-next-line no-console - console.error('Cannot retrieve index pattern from saved search'); + try { + const serviceToUse = + jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics; + + if (indexPatternId !== undefined) { + // index pattern is being used + const indexPattern: IIndexPattern = await indexPatterns.get(indexPatternId); + await serviceToUse.initializeFromIndexPattern(indexPattern); + resolve(serviceToUse.newJobCaps); + } else if (savedSearchId !== undefined) { + // saved search is being used + // load the index pattern from the saved search + const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId); + + if (indexPattern === null) { + // eslint-disable-next-line no-console + console.error('Cannot retrieve index pattern from saved search'); + reject(); + return; + } + + await serviceToUse.initializeFromIndexPattern(indexPattern); + resolve(serviceToUse.newJobCaps); + } else { reject(); - return; } - await serviceToUse.initializeFromIndexPattern(indexPattern); - resolve(serviceToUse.newJobCaps); - } else { - reject(); + } catch (error) { + reject(error); } }); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx index eb39ba4ab29a..5090274ca738 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx @@ -25,33 +25,34 @@ export async function resolveEmbeddableAnomalyChartsUserInput( const anomalyDetectorService = new AnomalyDetectorService(new HttpService(http)); return new Promise(async (resolve, reject) => { - const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); - - const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds); - const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); - const influencers = anomalyDetectorService.extractInfluencers(jobs); - influencers.push(VIEW_BY_JOB_LABEL); - - const modalSession = overlays.openModal( - toMountPoint( - { - modalSession.close(); - - resolve({ - jobIds, - title: panelTitle, - maxSeriesToPlot, - }); - }} - onCancel={() => { - modalSession.close(); - reject(); - }} - /> - ) - ); + try { + const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); + const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds); + const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); + const influencers = anomalyDetectorService.extractInfluencers(jobs); + influencers.push(VIEW_BY_JOB_LABEL); + const modalSession = overlays.openModal( + toMountPoint( + { + modalSession.close(); + resolve({ + jobIds, + title: panelTitle, + maxSeriesToPlot, + }); + }} + onCancel={() => { + modalSession.close(); + reject(); + }} + /> + ) + ); + } catch (error) { + reject(error); + } }); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx index e183907def57..5027eb6783a6 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx @@ -25,31 +25,36 @@ export async function resolveAnomalySwimlaneUserInput( const anomalyDetectorService = new AnomalyDetectorService(new HttpService(http)); return new Promise(async (resolve, reject) => { - const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); - - const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds); - - const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); - - const influencers = anomalyDetectorService.extractInfluencers(jobs); - influencers.push(VIEW_BY_JOB_LABEL); - - const modalSession = overlays.openModal( - toMountPoint( - { - modalSession.close(); - resolve({ jobIds, title: panelTitle, swimlaneType, viewBy }); - }} - onCancel={() => { - modalSession.close(); - reject(); - }} - /> - ) - ); + try { + const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds); + const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds); + const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); + const influencers = anomalyDetectorService.extractInfluencers(jobs); + influencers.push(VIEW_BY_JOB_LABEL); + const modalSession = overlays.openModal( + toMountPoint( + { + modalSession.close(); + resolve({ + jobIds, + title: panelTitle, + swimlaneType, + viewBy, + }); + }} + onCancel={() => { + modalSession.close(); + reject(); + }} + /> + ) + ); + } catch (error) { + reject(error); + } }); } diff --git a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx index 183388344785..fbceeb7f7cf7 100644 --- a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx +++ b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx @@ -38,56 +38,65 @@ export async function resolveJobSelection( } = coreStart; return new Promise(async (resolve, reject) => { - const maps = { - groupsMap: getInitialGroupsMap([]), - jobsMap: {}, - }; + try { + const maps = { + groupsMap: getInitialGroupsMap([]), + jobsMap: {}, + }; + const tzConfig = uiSettings.get('dateFormat:tz'); + const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : moment.tz.guess(); - const tzConfig = uiSettings.get('dateFormat:tz'); - const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : moment.tz.guess(); - - const onFlyoutClose = () => { - flyoutSession.close(); - reject(); - }; - - const onSelectionConfirmed = async ({ - jobIds, - groups, - }: { - jobIds: string[]; - groups: Array<{ groupId: string; jobIds: string[] }>; - }) => { - await flyoutSession.close(); - resolve({ jobIds, groups }); - }; - const flyoutSession = coreStart.overlays.openFlyout( - toMountPoint( - - - - ), - { - 'data-test-subj': 'mlFlyoutJobSelector', - ownFocus: true, - closeButtonAriaLabel: 'jobSelectorFlyout', - } - ); - - // Close the flyout when user navigates out of the dashboard plugin - currentAppId$.pipe(takeUntil(from(flyoutSession.onClose))).subscribe((appId) => { - if (appId !== DashboardConstants.DASHBOARDS_ID) { + const onFlyoutClose = () => { flyoutSession.close(); - } - }); + reject(); + }; + + const onSelectionConfirmed = async ({ + jobIds, + groups, + }: { + jobIds: string[]; + groups: Array<{ + groupId: string; + jobIds: string[]; + }>; + }) => { + await flyoutSession.close(); + resolve({ + jobIds, + groups, + }); + }; + + const flyoutSession = coreStart.overlays.openFlyout( + toMountPoint( + + + + ), + { + 'data-test-subj': 'mlFlyoutJobSelector', + ownFocus: true, + closeButtonAriaLabel: 'jobSelectorFlyout', + } + ); // Close the flyout when user navigates out of the dashboard plugin + + currentAppId$.pipe(takeUntil(from(flyoutSession.onClose))).subscribe((appId) => { + if (appId !== DashboardConstants.DASHBOARDS_ID) { + flyoutSession.close(); + } + }); + } catch (error) { + reject(error); + } }); } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js index 32662ae0efa3..a4645edda73d 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js @@ -98,27 +98,39 @@ async function getPaginatedThroughputData(pipelines, req, lsIndexPattern, throug const metricSeriesData = Object.values( await Promise.all( pipelines.map((pipeline) => { - return new Promise(async (resolve) => { - const data = await getMetrics( - req, - lsIndexPattern, - [throughputMetric], - [ - { - bool: { - should: [ - { term: { type: 'logstash_stats' } }, - { term: { 'metricset.name': 'stats' } }, - ], + return new Promise(async (resolve, reject) => { + try { + const data = await getMetrics( + req, + lsIndexPattern, + [throughputMetric], + [ + { + bool: { + should: [ + { + term: { + type: 'logstash_stats', + }, + }, + { + term: { + 'metricset.name': 'stats', + }, + }, + ], + }, }, + ], + { + pipeline, }, - ], - { - pipeline, - }, - 2 - ); - resolve(reduceData(pipeline, data)); + 2 + ); + resolve(reduceData(pipeline, data)); + } catch (error) { + reject(error); + } }); }) ) @@ -184,27 +196,38 @@ async function getPipelines(req, lsIndexPattern, pipelines, throughputMetric, no async function getThroughputPipelines(req, lsIndexPattern, pipelines, throughputMetric) { const metricsResponse = await Promise.all( pipelines.map((pipeline) => { - return new Promise(async (resolve) => { - const data = await getMetrics( - req, - lsIndexPattern, - [throughputMetric], - [ - { - bool: { - should: [ - { term: { type: 'logstash_stats' } }, - { term: { 'metricset.name': 'stats' } }, - ], + return new Promise(async (resolve, reject) => { + try { + const data = await getMetrics( + req, + lsIndexPattern, + [throughputMetric], + [ + { + bool: { + should: [ + { + term: { + type: 'logstash_stats', + }, + }, + { + term: { + 'metricset.name': 'stats', + }, + }, + ], + }, }, - }, - ], - { - pipeline, - } - ); - - resolve(reduceData(pipeline, data)); + ], + { + pipeline, + } + ); + resolve(reduceData(pipeline, data)); + } catch (error) { + reject(error); + } }); }) ); diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 9093d5d2e0db..f1a9296177d9 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -317,69 +317,73 @@ export class SessionIndex { const sessionIndexTemplateName = `${this.options.kibanaIndexName}_security_session_index_template_${SESSION_INDEX_TEMPLATE_VERSION}`; return (this.indexInitialization = new Promise(async (resolve, reject) => { - // Check if required index template exists. - let indexTemplateExists = false; try { - indexTemplateExists = ( - await this.options.elasticsearchClient.indices.existsTemplate({ - name: sessionIndexTemplateName, - }) - ).body; - } catch (err) { - this.options.logger.error( - `Failed to check if session index template exists: ${err.message}` - ); - return reject(err); - } - - // Create index template if it doesn't exist. - if (indexTemplateExists) { - this.options.logger.debug('Session index template already exists.'); - } else { + // Check if required index template exists. + let indexTemplateExists = false; try { - await this.options.elasticsearchClient.indices.putTemplate({ - name: sessionIndexTemplateName, - body: getSessionIndexTemplate(this.indexName), - }); - this.options.logger.debug('Successfully created session index template.'); + indexTemplateExists = ( + await this.options.elasticsearchClient.indices.existsTemplate({ + name: sessionIndexTemplateName, + }) + ).body; } catch (err) { - this.options.logger.error(`Failed to create session index template: ${err.message}`); + this.options.logger.error( + `Failed to check if session index template exists: ${err.message}` + ); return reject(err); } - } - // Check if required index exists. We cannot be sure that automatic creation of indices is - // always enabled, so we create session index explicitly. - let indexExists = false; - try { - indexExists = ( - await this.options.elasticsearchClient.indices.exists({ index: this.indexName }) - ).body; - } catch (err) { - this.options.logger.error(`Failed to check if session index exists: ${err.message}`); - return reject(err); - } - - // Create index if it doesn't exist. - if (indexExists) { - this.options.logger.debug('Session index already exists.'); - } else { - try { - await this.options.elasticsearchClient.indices.create({ index: this.indexName }); - this.options.logger.debug('Successfully created session index.'); - } catch (err) { - // There can be a race condition if index is created by another Kibana instance. - if (err?.body?.error?.type === 'resource_already_exists_exception') { - this.options.logger.debug('Session index already exists.'); - } else { - this.options.logger.error(`Failed to create session index: ${err.message}`); + // Create index template if it doesn't exist. + if (indexTemplateExists) { + this.options.logger.debug('Session index template already exists.'); + } else { + try { + await this.options.elasticsearchClient.indices.putTemplate({ + name: sessionIndexTemplateName, + body: getSessionIndexTemplate(this.indexName), + }); + this.options.logger.debug('Successfully created session index template.'); + } catch (err) { + this.options.logger.error(`Failed to create session index template: ${err.message}`); return reject(err); } } - } - // Notify any consumers that are awaiting on this promise and immediately reset it. - resolve(); + // Check if required index exists. We cannot be sure that automatic creation of indices is + // always enabled, so we create session index explicitly. + let indexExists = false; + try { + indexExists = ( + await this.options.elasticsearchClient.indices.exists({ index: this.indexName }) + ).body; + } catch (err) { + this.options.logger.error(`Failed to check if session index exists: ${err.message}`); + return reject(err); + } + + // Create index if it doesn't exist. + if (indexExists) { + this.options.logger.debug('Session index already exists.'); + } else { + try { + await this.options.elasticsearchClient.indices.create({ index: this.indexName }); + this.options.logger.debug('Successfully created session index.'); + } catch (err) { + // There can be a race condition if index is created by another Kibana instance. + if (err?.body?.error?.type === 'resource_already_exists_exception') { + this.options.logger.debug('Session index already exists.'); + } else { + this.options.logger.error(`Failed to create session index: ${err.message}`); + return reject(err); + } + } + } + + // Notify any consumers that are awaiting on this promise and immediately reset it. + resolve(); + } catch (error) { + reject(error); + } }).finally(() => { this.indexInitialization = undefined; })); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 53bebf340c26..d3193900859f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -128,208 +128,225 @@ export const importRulesRoute = ( const batchParseObjects = chunkParseObjects.shift() ?? []; const newImportRuleResponse = await Promise.all( batchParseObjects.reduce>>((accum, parsedRule) => { - const importsWorkerPromise = new Promise(async (resolve) => { - if (parsedRule instanceof Error) { - // If the JSON object had a validation or parse error then we return - // early with the error and an (unknown) for the ruleId - resolve( - createBulkErrorObject({ - statusCode: 400, - message: parsedRule.message, - }) - ); - return null; - } - const { - anomaly_threshold: anomalyThreshold, - author, - building_block_type: buildingBlockType, - description, - enabled, - event_category_override: eventCategoryOverride, - false_positives: falsePositives, - from, - immutable, - query: queryOrUndefined, - language: languageOrUndefined, - license, - machine_learning_job_id: machineLearningJobId, - output_index: outputIndex, - saved_id: savedId, - meta, - filters: filtersRest, - rule_id: ruleId, - index, - interval, - max_signals: maxSignals, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - severity, - severity_mapping: severityMapping, - tags, - threat, - threat_filters: threatFilters, - threat_index: threatIndex, - threat_query: threatQuery, - threat_mapping: threatMapping, - threat_language: threatLanguage, - threat_indicator_path: threatIndicatorPath, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - threshold, - timestamp_override: timestampOverride, - to, - type, - references, - note, - timeline_id: timelineId, - timeline_title: timelineTitle, - throttle, - version, - exceptions_list: exceptionsList, - } = parsedRule; + const importsWorkerPromise = new Promise( + async (resolve, reject) => { + try { + if (parsedRule instanceof Error) { + // If the JSON object had a validation or parse error then we return + // early with the error and an (unknown) for the ruleId + resolve( + createBulkErrorObject({ + statusCode: 400, + message: parsedRule.message, + }) + ); + return null; + } - try { - const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined; - - const language = - !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined; - - // TODO: Fix these either with an is conversion or by better typing them within io-ts - const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; - - throwHttpError(await mlAuthz.validateRuleType(type)); - - const rule = await readRules({ rulesClient, ruleId, id: undefined }); - if (rule == null) { - await createRules({ - rulesClient, - anomalyThreshold, + const { + anomaly_threshold: anomalyThreshold, author, - buildingBlockType, + building_block_type: buildingBlockType, description, enabled, - eventCategoryOverride, - falsePositives, + event_category_override: eventCategoryOverride, + false_positives: falsePositives, from, immutable, - query, - language, + query: queryOrUndefined, + language: languageOrUndefined, license, - machineLearningJobId, - outputIndex: signalsIndex, - savedId, - timelineId, - timelineTitle, + machine_learning_job_id: machineLearningJobId, + output_index: outputIndex, + saved_id: savedId, meta, - filters, - ruleId, + filters: filtersRest, + rule_id: ruleId, index, interval, - maxSignals, - name, - riskScore, - riskScoreMapping, - ruleNameOverride, - severity, - severityMapping, - tags, - throttle, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - timestampOverride, - references, - note, - version, - exceptionsList, - actions: [], // Actions are not imported nor exported at this time - }); - resolve({ rule_id: ruleId, status_code: 200 }); - } else if (rule != null && request.query.overwrite) { - await patchRules({ - rulesClient, - author, - buildingBlockType, - spaceId: context.securitySolution.getSpaceId(), - ruleStatusClient, - description, - enabled, - eventCategoryOverride, - falsePositives, - from, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - rule, - index, - interval, - maxSignals, - riskScore, - riskScoreMapping, - ruleNameOverride, + max_signals: maxSignals, + risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, - severityMapping, + severity_mapping: severityMapping, tags, - timestampOverride, - throttle, + threat, + threat_filters: threatFilters, + threat_index: threatIndex, + threat_query: threatQuery, + threat_mapping: threatMapping, + threat_language: threatLanguage, + threat_indicator_path: threatIndicatorPath, + concurrent_searches: concurrentSearches, + items_per_search: itemsPerSearch, + threshold, + timestamp_override: timestampOverride, to, type, - threat, - threshold, - threatFilters, - threatIndex, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, references, note, + timeline_id: timelineId, + timeline_title: timelineTitle, + throttle, version, - exceptionsList, - anomalyThreshold, - machineLearningJobId, - actions: undefined, - }); - resolve({ rule_id: ruleId, status_code: 200 }); - } else if (rule != null) { - resolve( - createBulkErrorObject({ + exceptions_list: exceptionsList, + } = parsedRule; + + try { + const query = + !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined; + const language = + !isMlRule(type) && languageOrUndefined == null + ? 'kuery' + : languageOrUndefined; // TODO: Fix these either with an is conversion or by better typing them within io-ts + + const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; + throwHttpError(await mlAuthz.validateRuleType(type)); + const rule = await readRules({ + rulesClient, ruleId, - statusCode: 409, - message: `rule_id: "${ruleId}" already exists`, - }) - ); + id: undefined, + }); + + if (rule == null) { + await createRules({ + rulesClient, + anomalyThreshold, + author, + buildingBlockType, + description, + enabled, + eventCategoryOverride, + falsePositives, + from, + immutable, + query, + language, + license, + machineLearningJobId, + outputIndex: signalsIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + ruleId, + index, + interval, + maxSignals, + name, + riskScore, + riskScoreMapping, + ruleNameOverride, + severity, + severityMapping, + tags, + throttle, + to, + type, + threat, + threshold, + threatFilters, + threatIndex, + threatIndicatorPath, + threatQuery, + threatMapping, + threatLanguage, + concurrentSearches, + itemsPerSearch, + timestampOverride, + references, + note, + version, + exceptionsList, + actions: [], // Actions are not imported nor exported at this time + }); + resolve({ + rule_id: ruleId, + status_code: 200, + }); + } else if (rule != null && request.query.overwrite) { + await patchRules({ + rulesClient, + author, + buildingBlockType, + spaceId: context.securitySolution.getSpaceId(), + ruleStatusClient, + description, + enabled, + eventCategoryOverride, + falsePositives, + from, + query, + language, + license, + outputIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + rule, + index, + interval, + maxSignals, + riskScore, + riskScoreMapping, + ruleNameOverride, + name, + severity, + severityMapping, + tags, + timestampOverride, + throttle, + to, + type, + threat, + threshold, + threatFilters, + threatIndex, + threatQuery, + threatMapping, + threatLanguage, + concurrentSearches, + itemsPerSearch, + references, + note, + version, + exceptionsList, + anomalyThreshold, + machineLearningJobId, + actions: undefined, + }); + resolve({ + rule_id: ruleId, + status_code: 200, + }); + } else if (rule != null) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: 409, + message: `rule_id: "${ruleId}" already exists`, + }) + ); + } + } catch (err) { + resolve( + createBulkErrorObject({ + ruleId, + statusCode: err.statusCode ?? 400, + message: err.message, + }) + ); + } + } catch (error) { + reject(error); } - } catch (err) { - resolve( - createBulkErrorObject({ - ruleId, - statusCode: err.statusCode ?? 400, - message: err.message, - }) - ); } - }); + ); return [...accum, importsWorkerPromise]; }, []) ); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts index 70d93d7552b1..7e35c2163df7 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts @@ -106,133 +106,132 @@ export const importTimelines = async ( batchParseObjects.reduce>>((accum, parsedTimeline) => { const importsWorkerPromise = new Promise( async (resolve, reject) => { - if (parsedTimeline instanceof Error) { - // If the JSON object had a validation or parse error then we return - // early with the error and an (unknown) for the ruleId - resolve( - createBulkErrorObject({ - statusCode: 400, - message: parsedTimeline.message, - }) - ); - - return null; - } - - const { - savedObjectId, - pinnedEventIds, - globalNotes, - eventNotes, - status, - templateTimelineId, - templateTimelineVersion, - title, - timelineType, - version, - } = parsedTimeline; - - const parsedTimelineObject = omit(timelineSavedObjectOmittedFields, parsedTimeline); - let newTimeline = null; try { - const compareTimelinesStatus = new CompareTimelinesStatus({ - status, - timelineType, - title, - timelineInput: { - id: savedObjectId, - version, - }, - templateTimelineInput: { - id: templateTimelineId, - version: templateTimelineVersion, - }, - frameworkRequest, - }); - await compareTimelinesStatus.init(); - const isTemplateTimeline = compareTimelinesStatus.isHandlingTemplateTimeline; - if (compareTimelinesStatus.isCreatableViaImport) { - // create timeline / timeline template - newTimeline = await createTimelines({ - frameworkRequest, - timeline: setTimeline(parsedTimelineObject, parsedTimeline, isTemplateTimeline), - pinnedEventIds: isTemplateTimeline ? null : pinnedEventIds, - notes: isTemplateTimeline ? globalNotes : [...globalNotes, ...eventNotes], - isImmutable, - overrideNotesOwner: false, - }); - - resolve({ - timeline_id: newTimeline.timeline.savedObjectId, - status_code: 200, - action: TimelineStatusActions.createViaImport, - }); - } - - if (!compareTimelinesStatus.isHandlingTemplateTimeline) { - const errorMessage = compareTimelinesStatus.checkIsFailureCases( - TimelineStatusActions.createViaImport - ); - const message = errorMessage?.body ?? DEFAULT_ERROR; - + if (parsedTimeline instanceof Error) { + // If the JSON object had a validation or parse error then we return + // early with the error and an (unknown) for the ruleId resolve( createBulkErrorObject({ - id: savedObjectId ?? 'unknown', - statusCode: 409, - message, + statusCode: 400, + message: parsedTimeline.message, }) ); - } else { - if (compareTimelinesStatus.isUpdatableViaImport) { - // update timeline template + return null; + } + + const { + savedObjectId, + pinnedEventIds, + globalNotes, + eventNotes, + status, + templateTimelineId, + templateTimelineVersion, + title, + timelineType, + version, + } = parsedTimeline; + const parsedTimelineObject = omit(timelineSavedObjectOmittedFields, parsedTimeline); + let newTimeline = null; + + try { + const compareTimelinesStatus = new CompareTimelinesStatus({ + status, + timelineType, + title, + timelineInput: { + id: savedObjectId, + version, + }, + templateTimelineInput: { + id: templateTimelineId, + version: templateTimelineVersion, + }, + frameworkRequest, + }); + await compareTimelinesStatus.init(); + const isTemplateTimeline = compareTimelinesStatus.isHandlingTemplateTimeline; + + if (compareTimelinesStatus.isCreatableViaImport) { + // create timeline / timeline template newTimeline = await createTimelines({ frameworkRequest, - timeline: parsedTimelineObject, - timelineSavedObjectId: compareTimelinesStatus.timelineId, - timelineVersion: compareTimelinesStatus.timelineVersion, - notes: globalNotes, - existingNoteIds: compareTimelinesStatus.timelineInput.data?.noteIds, + timeline: setTimeline(parsedTimelineObject, parsedTimeline, isTemplateTimeline), + pinnedEventIds: isTemplateTimeline ? null : pinnedEventIds, + notes: isTemplateTimeline ? globalNotes : [...globalNotes, ...eventNotes], isImmutable, overrideNotesOwner: false, }); - resolve({ timeline_id: newTimeline.timeline.savedObjectId, status_code: 200, - action: TimelineStatusActions.updateViaImport, + action: TimelineStatusActions.createViaImport, }); - } else { + } + + if (!compareTimelinesStatus.isHandlingTemplateTimeline) { const errorMessage = compareTimelinesStatus.checkIsFailureCases( - TimelineStatusActions.updateViaImport + TimelineStatusActions.createViaImport ); - const message = errorMessage?.body ?? DEFAULT_ERROR; - resolve( createBulkErrorObject({ - id: - savedObjectId ?? - (templateTimelineId - ? `(template_timeline_id) ${templateTimelineId}` - : 'unknown'), + id: savedObjectId ?? 'unknown', statusCode: 409, message, }) ); + } else { + if (compareTimelinesStatus.isUpdatableViaImport) { + // update timeline template + newTimeline = await createTimelines({ + frameworkRequest, + timeline: parsedTimelineObject, + timelineSavedObjectId: compareTimelinesStatus.timelineId, + timelineVersion: compareTimelinesStatus.timelineVersion, + notes: globalNotes, + existingNoteIds: compareTimelinesStatus.timelineInput.data?.noteIds, + isImmutable, + overrideNotesOwner: false, + }); + resolve({ + timeline_id: newTimeline.timeline.savedObjectId, + status_code: 200, + action: TimelineStatusActions.updateViaImport, + }); + } else { + const errorMessage = compareTimelinesStatus.checkIsFailureCases( + TimelineStatusActions.updateViaImport + ); + const message = errorMessage?.body ?? DEFAULT_ERROR; + resolve( + createBulkErrorObject({ + id: + savedObjectId ?? + (templateTimelineId + ? `(template_timeline_id) ${templateTimelineId}` + : 'unknown'), + statusCode: 409, + message, + }) + ); + } } + } catch (err) { + resolve( + createBulkErrorObject({ + id: + savedObjectId ?? + (templateTimelineId + ? `(template_timeline_id) ${templateTimelineId}` + : 'unknown'), + statusCode: 400, + message: err.message, + }) + ); } - } catch (err) { - resolve( - createBulkErrorObject({ - id: - savedObjectId ?? - (templateTimelineId - ? `(template_timeline_id) ${templateTimelineId}` - : 'unknown'), - statusCode: 400, - message: err.message, - }) - ); + } catch (error) { + reject(error); } } ); diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index 82a111305927..e477edf3b9ae 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -47,62 +47,62 @@ describe('Configuration Statistics Aggregator', () => { }; return new Promise(async (resolve, reject) => { - createConfigurationAggregator(configuration, managedConfig) - .pipe(take(3), bufferCount(3)) - .subscribe(([initial, updatedWorkers, updatedInterval]) => { - expect(initial.value).toEqual({ - max_workers: 10, - poll_interval: 6000000, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, + try { + createConfigurationAggregator(configuration, managedConfig) + .pipe(take(3), bufferCount(3)) + .subscribe(([initial, updatedWorkers, updatedInterval]) => { + expect(initial.value).toEqual({ + max_workers: 10, + poll_interval: 6000000, + max_poll_inactivity_cycles: 10, + request_capacity: 1000, + monitored_aggregated_stats_refresh_rate: 5000, + monitored_stats_running_average_window: 50, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, }, - custom: {}, - }, - }); - - expect(updatedWorkers.value).toEqual({ - max_workers: 8, - poll_interval: 6000000, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, + }); + expect(updatedWorkers.value).toEqual({ + max_workers: 8, + poll_interval: 6000000, + max_poll_inactivity_cycles: 10, + request_capacity: 1000, + monitored_aggregated_stats_refresh_rate: 5000, + monitored_stats_running_average_window: 50, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, }, - custom: {}, - }, - }); - - expect(updatedInterval.value).toEqual({ - max_workers: 8, - poll_interval: 3000, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, + }); + expect(updatedInterval.value).toEqual({ + max_workers: 8, + poll_interval: 3000, + max_poll_inactivity_cycles: 10, + request_capacity: 1000, + monitored_aggregated_stats_refresh_rate: 5000, + monitored_stats_running_average_window: 50, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, }, - custom: {}, - }, - }); - resolve(); - }, reject); - - managedConfig.maxWorkersConfiguration$.next(8); - - managedConfig.pollIntervalConfiguration$.next(3000); + }); + resolve(); + }, reject); + managedConfig.maxWorkersConfiguration$.next(8); + managedConfig.pollIntervalConfiguration$.next(3000); + } catch (error) { + reject(error); + } }); }); }); diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index 9125bca8f5b0..d24931646128 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -328,27 +328,44 @@ describe('Workload Statistics Aggregator', () => { loggingSystemMock.create().get() ); - return new Promise(async (resolve) => { - workloadAggregator.pipe(first()).subscribe((result) => { - expect(result.key).toEqual('workload'); - expect(result.value).toMatchObject({ - count: 4, - task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, - }, + return new Promise(async (resolve, reject) => { + try { + workloadAggregator.pipe(first()).subscribe((result) => { + expect(result.key).toEqual('workload'); + expect(result.value).toMatchObject({ + count: 4, + task_types: { + actions_telemetry: { + count: 2, + status: { + idle: 2, + }, + }, + alerting_telemetry: { + count: 1, + status: { + idle: 1, + }, + }, + session_cleanup: { + count: 1, + status: { + idle: 1, + }, + }, + }, + }); + resolve(); }); - resolve(); - }); - - availability$.next(false); - - await sleep(10); - expect(taskStore.aggregate).not.toHaveBeenCalled(); - await sleep(10); - expect(taskStore.aggregate).not.toHaveBeenCalled(); - availability$.next(true); + availability$.next(false); + await sleep(10); + expect(taskStore.aggregate).not.toHaveBeenCalled(); + await sleep(10); + expect(taskStore.aggregate).not.toHaveBeenCalled(); + availability$.next(true); + } catch (error) { + reject(error); + } }); }); diff --git a/x-pack/plugins/task_manager/server/task_scheduling.ts b/x-pack/plugins/task_manager/server/task_scheduling.ts index 88176b25680c..a89f66d9c772 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.ts @@ -113,11 +113,18 @@ export class TaskScheduling { */ public async runNow(taskId: string): Promise { return new Promise(async (resolve, reject) => { - this.awaitTaskRunResult(taskId) - // don't expose state on runNow - .then(({ id }) => resolve({ id })) - .catch(reject); - this.taskPollingLifecycle.attemptToRun(taskId); + try { + this.awaitTaskRunResult(taskId) // don't expose state on runNow + .then(({ id }) => + resolve({ + id, + }) + ) + .catch(reject); + this.taskPollingLifecycle.attemptToRun(taskId); + } catch (error) { + reject(error); + } }); } @@ -137,39 +144,42 @@ export class TaskScheduling { taskInstance: task, }); return new Promise(async (resolve, reject) => { - // The actual promise returned from this function is resolved after the awaitTaskRunResult promise resolves. - // However, we do not wait to await this promise, as we want later execution to happen in parallel. - // The awaitTaskRunResult promise is resolved once the ephemeral task is successfully executed (technically, when a TaskEventType.TASK_RUN is emitted with the same id). - // However, the ephemeral task won't even get into the queue until the subsequent this.ephemeralTaskLifecycle.attemptToRun is called (which puts it in the queue). - - // The reason for all this confusion? Timing. - - // In the this.ephemeralTaskLifecycle.attemptToRun, it's possible that the ephemeral task is put into the queue and processed before this function call returns anything. - // If that happens, putting the awaitTaskRunResult after would just hang because the task already completed. We need to listen for the completion before we add it to the queue to avoid this possibility. - const { cancel, resolveOnCancel } = cancellablePromise(); - this.awaitTaskRunResult(id, resolveOnCancel) - .then((arg: RunNowResult) => { - resolve(arg); - }) - .catch((err: Error) => { - reject(err); + try { + // The actual promise returned from this function is resolved after the awaitTaskRunResult promise resolves. + // However, we do not wait to await this promise, as we want later execution to happen in parallel. + // The awaitTaskRunResult promise is resolved once the ephemeral task is successfully executed (technically, when a TaskEventType.TASK_RUN is emitted with the same id). + // However, the ephemeral task won't even get into the queue until the subsequent this.ephemeralTaskLifecycle.attemptToRun is called (which puts it in the queue). + // The reason for all this confusion? Timing. + // In the this.ephemeralTaskLifecycle.attemptToRun, it's possible that the ephemeral task is put into the queue and processed before this function call returns anything. + // If that happens, putting the awaitTaskRunResult after would just hang because the task already completed. We need to listen for the completion before we add it to the queue to avoid this possibility. + const { cancel, resolveOnCancel } = cancellablePromise(); + this.awaitTaskRunResult(id, resolveOnCancel) + .then((arg: RunNowResult) => { + resolve(arg); + }) + .catch((err: Error) => { + reject(err); + }); + const attemptToRunResult = this.ephemeralTaskLifecycle.attemptToRun({ + id, + scheduledAt: new Date(), + runAt: new Date(), + status: TaskStatus.Idle, + ownerId: this.taskManagerId, + ...modifiedTask, }); - const attemptToRunResult = this.ephemeralTaskLifecycle.attemptToRun({ - id, - scheduledAt: new Date(), - runAt: new Date(), - status: TaskStatus.Idle, - ownerId: this.taskManagerId, - ...modifiedTask, - }); - if (isErr(attemptToRunResult)) { - cancel(); - reject( - new EphemeralTaskRejectedDueToCapacityError( - `Ephemeral Task of type ${task.taskType} was rejected`, - task - ) - ); + + if (isErr(attemptToRunResult)) { + cancel(); + reject( + new EphemeralTaskRejectedDueToCapacityError( + `Ephemeral Task of type ${task.taskType} was rejected`, + task + ) + ); + } + } catch (error) { + reject(error); } }); } diff --git a/x-pack/test/functional_enterprise_search/services/app_search_client.ts b/x-pack/test/functional_enterprise_search/services/app_search_client.ts index 8e829b97e9dd..457523bccf8c 100644 --- a/x-pack/test/functional_enterprise_search/services/app_search_client.ts +++ b/x-pack/test/functional_enterprise_search/services/app_search_client.ts @@ -105,14 +105,20 @@ const search = async (engineName: string): Promise => { // Since the App Search API does not issue document receipts, the only way to tell whether or not documents // are fully indexed is to poll the search endpoint. export const waitForIndexedDocs = (engineName: string) => { - return new Promise(async function (resolve) { - let isReady = false; - while (!isReady) { - const response = await search(engineName); - if (response.results && response.results.length > 0) { - isReady = true; - resolve(); + return new Promise(async function (resolve, reject) { + try { + let isReady = false; + + while (!isReady) { + const response = await search(engineName); + + if (response.results && response.results.length > 0) { + isReady = true; + resolve(); + } } + } catch (error) { + reject(error); } }); }; diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index 8975feb6fbe0..b3816ad7563b 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -116,21 +116,29 @@ export const waitFor = async ( timeoutWait: number = 10 ) => { await new Promise(async (resolve, reject) => { - let found = false; - let numberOfTries = 0; - while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { - const itPasses = await functionToTest(); - if (itPasses) { - found = true; - } else { - numberOfTries++; + try { + let found = false; + let numberOfTries = 0; + + while (!found && numberOfTries < Math.floor(maxTimeout / timeoutWait)) { + const itPasses = await functionToTest(); + + if (itPasses) { + found = true; + } else { + numberOfTries++; + } + + await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait)); } - await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait)); - } - if (found) { - resolve(); - } else { - reject(new Error(`timed out waiting for function ${functionName} condition to be true`)); + + if (found) { + resolve(); + } else { + reject(new Error(`timed out waiting for function ${functionName} condition to be true`)); + } + } catch (error) { + reject(error); } }); }; diff --git a/yarn.lock b/yarn.lock index ac8dc9686d86..f1c813d37fc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6457,6 +6457,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa" integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w== +"@typescript-eslint/types@4.28.3": + version "4.28.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.3.tgz#8fffd436a3bada422c2c1da56060a0566a9506c7" + integrity sha512-kQFaEsQBQVtA9VGVyciyTbIg7S3WoKHNuOp/UF5RG40900KtGqfoiETWD/v0lzRXc+euVE9NXmfer9dLkUJrkA== + "@typescript-eslint/types@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.3.0.tgz#1f0b2d5e140543e2614f06d48fb3ae95193c6ddf" @@ -6490,6 +6495,19 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@^4.14.1": + version "4.28.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.3.tgz#253d7088100b2a38aefe3c8dd7bd1f8232ec46fb" + integrity sha512-YAb1JED41kJsqCQt1NcnX5ZdTA93vKFCMP4lQYG6CFxd0VzDJcKttRlMrlG+1qiWAw8+zowmHU1H0OzjWJzR2w== + dependencies: + "@typescript-eslint/types" "4.28.3" + "@typescript-eslint/visitor-keys" "4.28.3" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/visitor-keys@4.14.1": version "4.14.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91" @@ -6498,6 +6516,14 @@ "@typescript-eslint/types" "4.14.1" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.28.3": + version "4.28.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.3.tgz#26ac91e84b23529968361045829da80a4e5251c4" + integrity sha512-ri1OzcLnk1HH4gORmr1dllxDzzrN6goUIz/P4MHFV0YZJDCADPR3RvYNp0PW2SetKTThar6wlbFTL00hV2Q+fg== + dependencies: + "@typescript-eslint/types" "4.28.3" + eslint-visitor-keys "^2.0.0" + "@typescript-eslint/visitor-keys@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.3.0.tgz#0e5ab0a09552903edeae205982e8521e17635ae0" @@ -13120,6 +13146,11 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-traverse/-/eslint-traverse-1.0.0.tgz#108d360a171a6e6334e1af0cee905a93bd0dcc53" + integrity sha512-bSp37rQs93LF8rZ409EI369DGCI4tELbFVmFNxI6QbuveS7VRxYVyUhwDafKN/enMyUh88HQQ7ZoGUHtPuGdcw== + eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -27535,6 +27566,13 @@ tsutils@^3.17.1: dependencies: tslib "^1.8.1" +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"