diff --git a/package.json b/package.json index 03958f44c2fa9..26405562de226 100644 --- a/package.json +++ b/package.json @@ -427,6 +427,7 @@ "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", + "@babel/generator": "^7.12.11", "@babel/parser": "^7.12.11", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-export-namespace-from": "^7.12.1", @@ -651,6 +652,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", @@ -720,6 +722,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 0e686d01e2b8b..4b5c3c372fa23 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -82,5 +82,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 e5a38e5f09529..a7a9c6b5bebdf 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 0000000000000..317758fd3629a --- /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 0000000000000..f5929b1b3966f --- /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 dcf071719c11a..455d19956f7e8 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 86cb9198e0699..4c056e748f06e 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 386e48e745e80..201be8848bac8 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/kibana_react/public/util/mount_point_portal.test.tsx b/src/plugins/kibana_react/public/util/mount_point_portal.test.tsx index 53503b197567e..39e345568a298 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 6e32ff5d4e41e..8eafada176e7a 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 b1509c4effa7a..0017849408efb 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 558739a44dd41..45b9b4c7a885b 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 ee446c58c0013..e7b1940ec3e1f 100644 --- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts +++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts @@ -70,35 +70,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; - - 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(); + const updateScope = () => { + if (!this.$scope) { + return; + } + + 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(); + } + } catch (error) { + reject(error); } }); } diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index 9ce5a5339c04f..2c26dfc3ae276 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx +++ b/src/plugins/vis_type_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); - } - }); - - this.setState({ filterableLabels }, resolve); + new Promise(async (resolve, reject) => { + try { + const filterableLabels = new Set(); + items.forEach(async (item) => { + const canFilter = await this.canFilter(item); + + 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 57f0872d62589..f04c611c2fae9 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 9c7c6ff1e5180..192a7ffb5e782 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 540da3e2b9fdc..cf1f9e1bc39e8 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 3e5e2d54422d6..c1095e9a61c57 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 a998343535249..d3b407c2bb65a 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 eb39ba4ab29aa..5090274ca7383 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 e183907def57b..5027eb6783a64 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 1833883447859..fbceeb7f7cf79 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 onFlyoutClose = () => { - flyoutSession.close(); - reject(); - }; + const onSelectionConfirmed = async ({ + jobIds, + groups, + }: { + jobIds: string[]; + groups: Array<{ + groupId: string; + jobIds: string[]; + }>; + }) => { + await flyoutSession.close(); + resolve({ + jobIds, + groups, + }); + }; - 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', - } - ); + 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 - // 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(); - } - }); + 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 32662ae0efa34..a4645edda73d0 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 9093d5d2e0db2..f1a9296177d9c 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 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); + } + } - // Create index if it doesn't exist. - if (indexExists) { - this.options.logger.debug('Session index already exists.'); - } else { + // 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 { - await this.options.elasticsearchClient.indices.create({ index: this.indexName }); - this.options.logger.debug('Successfully created session index.'); + indexExists = ( + await this.options.elasticsearchClient.indices.exists({ index: this.indexName }) + ).body; } 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); + 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(); + // 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 8e322405280d3..7eb3220bc4387 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 @@ -127,204 +127,221 @@ 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, - version, - 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[]; + 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; + } - throwHttpError(await mlAuthz.validateRuleType(type)); - - const rule = await readRules({ alertsClient, ruleId, id: undefined }); - if (rule == null) { - await createRules({ - alertsClient, - 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, + max_signals: maxSignals, + risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, - riskScore, - riskScoreMapping, - ruleNameOverride, severity, - severityMapping, + severity_mapping: severityMapping, tags, - to, - type, 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, - 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({ - alertsClient, - author, - buildingBlockType, - savedObjectsClient, - 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, + timestamp_override: timestampOverride, to, type, - threat, - threshold, - threatFilters, - threatIndex, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, references, note, + timeline_id: timelineId, + timeline_title: timelineTitle, 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({ + alertsClient, ruleId, - statusCode: 409, - message: `rule_id: "${ruleId}" already exists`, - }) - ); + id: undefined, + }); + + if (rule == null) { + await createRules({ + alertsClient, + 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, + 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({ + alertsClient, + author, + buildingBlockType, + savedObjectsClient, + 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, + 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 70d93d7552b1c..7e35c2163df70 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({ + 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, - timelineType, + templateTimelineId, + templateTimelineVersion, 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, - }); + timelineType, + version, + } = parsedTimeline; + const parsedTimelineObject = omit(timelineSavedObjectOmittedFields, parsedTimeline); + let newTimeline = null; - resolve({ - timeline_id: newTimeline.timeline.savedObjectId, - status_code: 200, - action: TimelineStatusActions.createViaImport, + 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.isHandlingTemplateTimeline) { - const errorMessage = compareTimelinesStatus.checkIsFailureCases( - TimelineStatusActions.createViaImport - ); - const message = errorMessage?.body ?? DEFAULT_ERROR; - - resolve( - createBulkErrorObject({ - id: savedObjectId ?? 'unknown', - statusCode: 409, - message, - }) - ); - } else { - if (compareTimelinesStatus.isUpdatableViaImport) { - // update timeline template + 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 6aa8bad5717ec..23a25628aa640 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 @@ -43,62 +43,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 9125bca8f5b05..d24931646128a 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 153c16f5c4bf7..38773d011ac6f 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.ts @@ -100,8 +100,12 @@ export class TaskScheduling { */ public async runNow(taskId: string): Promise { return new Promise(async (resolve, reject) => { - this.awaitTaskRunResult(taskId).then(resolve).catch(reject); - this.taskPollingLifecycle.attemptToRun(taskId); + try { + this.awaitTaskRunResult(taskId).then(resolve).catch(reject); + this.taskPollingLifecycle.attemptToRun(taskId); + } 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 8e829b97e9dda..457523bccf8c3 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 90103ec8b856c..bcb219ec9744d 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -117,21 +117,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; + 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)); + } + + if (found) { + resolve(); } else { - numberOfTries++; + reject(new Error(`timed out waiting for function ${functionName} condition to be true`)); } - await new Promise((resolveTimeout) => setTimeout(resolveTimeout, timeoutWait)); - } - 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 f2b40bb08df1c..962b0ffbfd9f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,6 +110,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.12.11": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" + integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== + dependencies: + "@babel/types" "^7.14.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10": version "7.12.10" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz#54ab9b000e60a93644ce17b3f37d313aaf1d115d" @@ -293,6 +302,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== +"@babel/helper-validator-identifier@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" + integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -1187,6 +1201,14 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" + integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" @@ -6283,6 +6305,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" @@ -6316,6 +6343,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" @@ -6324,6 +6364,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" @@ -12962,6 +13010,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" @@ -24858,6 +24911,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -27215,6 +27275,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"