diff --git a/.i18nrc.json b/.i18nrc.json index fac9b9ce53184..a1c49ae03f359 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -17,6 +17,7 @@ "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "management": "src/legacy/core_plugins/management", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", diff --git a/NOTICE.txt b/NOTICE.txt index 989bb6f754a09..230e511746022 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -186,32 +186,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -This product includes code that is based on facebookincubator/idx, which was -available under a "MIT" license. - -MIT License - -Copyright (c) 2013-present, Facebook, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - --- This product includes code that was extracted from angular@1.3. Original license: diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 350a3c2a997cf..77a2bfe77b4ab 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -282,7 +282,7 @@ The `FunctionalTestRunner` comes with three built-in services: * Source: {blob}src/functional_test_runner/lib/lifecycle.ts[src/functional_test_runner/lib/lifecycle.ts] * Designed primary for use in services * Exposes lifecycle events for basic coordination. Handlers can return a promise and resolve/fail asynchronously -* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup`, `phaseStart`, `phaseEnd` +* Phases include: `beforeLoadTests`, `beforeTests`, `beforeEachTest`, `cleanup` [float] ===== Kibana Services diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 5605ed5c56688..39c87d97af4ba 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -147,6 +147,36 @@ will default to `true`. `logging.quiet:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output other than error messages. +`logging.rotate:`:: [experimental] Specifies the options for the logging rotate feature. +When not defined, all the sub options defaults would be applied. +The following example shows a valid logging rotate configuration: ++ +-- + logging.rotate: + enabled: true + everyBytes: 10485760 + keepFiles: 10 +-- + +`logging.rotate.enabled:`:: [experimental] *Default: false* Set the value of this setting to `true` to +enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` +that feature would not take any effect. + +`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the +limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and +this option should be at least greater than 1024. + +`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep +on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` +option has to be in the range of 2 to 1024 files. + +`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case +the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds. + +`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring +the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, +the `polling` method could be used enabling that option. + `logging.silent:`:: *Default: false* Set the value of this setting to `true` to suppress all logging output. diff --git a/package.json b/package.json index 847f09b4ab4cf..8ccb138a3aced 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "@elastic/charts": "^14.0.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -150,6 +150,7 @@ "cache-loader": "^4.1.0", "chalk": "^2.4.2", "check-disk-space": "^2.1.0", + "chokidar": "3.2.1", "color": "1.0.3", "commander": "3.0.0", "compare-versions": "3.5.1", @@ -360,8 +361,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -373,7 +374,6 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chokidar": "3.2.1", "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js index 98fa62021b5bb..a7bce739ba8e3 100644 --- a/packages/eslint-config-kibana/.eslintrc.js +++ b/packages/eslint-config-kibana/.eslintrc.js @@ -28,6 +28,11 @@ module.exports = { to: false, disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead` }, + { + from: '@kbn/elastic-idx', + to: false, + disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain` + }, { from: 'x-pack', toRelative: 'x-pack', diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index ee65a1cf79148..7917297883b03 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.9.0", - "@typescript-eslint/parser": "^2.9.0", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d1b7bc20dd9f9..0de9318ea0c27 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -35,13 +35,6 @@ const plugins = [ // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), ]; -const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test'; - -// Only load the idx plugin in non-test environments, since it conflicts with -// Jest's coverage mapping. -if (!isTestEnv) { - plugins.push(require.resolve('@kbn/elastic-idx/babel')); -} module.exports = { presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')], diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 1913301e21a76..e554859928c0b 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -12,7 +12,6 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", - "@kbn/elastic-idx": "1.0.0", "babel-plugin-add-module-exports": "^1.0.2", "babel-plugin-filter-imports": "^3.0.0", "babel-plugin-transform-define": "^1.3.1", diff --git a/packages/kbn-elastic-idx/.npmignore b/packages/kbn-elastic-idx/.npmignore deleted file mode 100644 index ece13b72c93ea..0000000000000 --- a/packages/kbn-elastic-idx/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -/tsconfig.json -/src -/babel/index.test.js diff --git a/packages/kbn-elastic-idx/README.md b/packages/kbn-elastic-idx/README.md deleted file mode 100644 index 81cd5d0f86e9b..0000000000000 --- a/packages/kbn-elastic-idx/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Kibana elastic-idx Library -========================== - -The `@kbn/elastic-idx` package provides the `idx` function used for optional -chaining. Currently, the optional chaining draft is in stage 1, making it too -uncertain to add syntax support within Kibana. Other optional chaining -libraries require the Proxy object to be polyfilled for browser support, -however, this polyfill is not fully supported across all browsers that Kibana -requires. The facebookincubator `idx` project -(https://github.com/facebookincubator/idx) provides an answer to this with a -specific implementation that is understood by TypeScript so that type -information does not get lost (unlike lodash get) The `@kbn/elastic-idx` -library makes use the `idx` idiom but differs in the way null values within the -property chain are handled. - -Similar to the facebookincubator `idx` project, `@kbn/elastic-idx` also -provides the Babel plugin to transform `idx()` function calls into the expanded -form. This Babel plugin was based off the facebookincubator `idx` Babel -plugin, since the invocation syntax is almost identical, but the transformed -code differs to match how the `@kbn/elastic-idx` library treats null values. - -App Usage ----------- -Within Kibana, `@kbn/elastic-idx` can be imported and used in any JavaScript or -TypeScript project: - -``` -import { idx } from '@kbn/elastic-idx'; - -const obj0 = { a: { b: { c: { d: 'iamdefined' } } } }; -const obj1 = { a: { b: null } }; - -idx(obj0, _ => _.a.b.c.d); // returns 'iamdefined' -idx(obj1, _ => _.a.b.c.e); // returns undefined -idx(obj1, _ => _.a.b); // returns null -``` - -Build Optimization -------------------- -Similar to the facebookincubator `idx` project, it is NOT RECOMMENDED to use -idx in shipped app code. The implementation details which make -`@kbn/elastic-idx` possible comes at a non-negligible performance cost. This -usually isn't noticable during development, but for production builds, it is -recommended to transform idx calls into native, expanded form JS. Use the -plugin `@kbn/elastic-idx/babel` within your Babel configuration: - -``` -{ "plugins": [ "@kbn/elastic-idx/babel" ] } -``` - -The resulting Babel transforms the following: - -``` -import { idx } from '@kbn/elastic-idx'; -const obj = { a: { b: { c: { d: 'iamdefined' } } } }; -idx(obj, _ => _.a.b.c.d); -``` - -into this: - -``` -obj != null && -obj.a != null && -obj.a.b != null && -obj.a.b.c != null ? -obj.a.b.c.d : undefined -``` - -Note that this also removes the import statement from the source code, since it -no longer needs to be bundled. - -Testing --------- - -Tests can be run with `npm test`. This includes "functional" tests that -transform and evaluate idx calls. diff --git a/packages/kbn-elastic-idx/babel/index.js b/packages/kbn-elastic-idx/babel/index.js deleted file mode 100644 index 89171e3565530..0000000000000 --- a/packages/kbn-elastic-idx/babel/index.js +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint strict: 0, new-cap: 0 */ - -'use strict'; -module.exports = context => { - const t = context.types; - - const idxRe = /\bidx\b/; - - function checkIdxArguments(file, node) { - const args = node.arguments; - if (args.length !== 2) { - throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.'); - } - const arrowFunction = args[1]; - if (!t.isArrowFunctionExpression(arrowFunction)) { - throw file.buildCodeFrameError( - arrowFunction, - 'The second argument supplied to `idx` must be an arrow function.' - ); - } - if (!t.isExpression(arrowFunction.body)) { - throw file.buildCodeFrameError( - arrowFunction.body, - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - } - if (arrowFunction.params.length !== 1) { - throw file.buildCodeFrameError( - arrowFunction.params[2] || arrowFunction, - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - } - const input = arrowFunction.params[0]; - if (!t.isIdentifier(input)) { - throw file.buildCodeFrameError( - arrowFunction.params[0], - 'The parameter supplied to `idx` must be an identifier.' - ); - } - } - - function checkIdxBindingNode(file, node) { - if (t.isImportDeclaration(node)) { - // E.g. `import '...'` - if (node.specifiers.length === 0) { - throw file.buildCodeFrameError(node, 'The idx import must have a value.'); - } - // E.g. `import A, {B} from '...'` - // `import A, * as B from '...'` - // `import {A, B} from '...'` - if (node.specifiers.length > 1) { - throw file.buildCodeFrameError( - node.specifiers[1], - 'The idx import must be a single specifier.' - ); - } - // `importKind` is not a property unless flow syntax is enabled. - // On specifiers, `importKind` is not "value" when it's not a type, it's - // `null`. - // E.g. `import type {...} from '...'` - // `import typeof {...} from '...'` - // `import {type ...} from '...'`. - // `import {typeof ...} from '...'` - if ( - node.importKind === 'type' || - node.importKind === 'typeof' || - node.specifiers[0].importKind === 'type' || - node.specifiers[0].importKind === 'typeof' - ) { - throw file.buildCodeFrameError(node, 'The idx import must be a value import.'); - } - } else if (t.isVariableDeclarator(node)) { - // E.g. var {idx} or var [idx] - if (!t.isIdentifier(node.id)) { - throw file.buildCodeFrameError( - node.specifiers[0], - 'The idx declaration must be an identifier.' - ); - } - } - } - - class UnsupportedNodeTypeError extends Error { - constructor(node, ...params) { - super(`Node type is not supported: ${node.type}`, ...params); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, UnsupportedNodeTypeError); - } - - this.name = 'UnsupportedNodeTypeError'; - } - } - - function getDeepProperties(node, properties = [], computedProperties = new Set()) { - if (t.isMemberExpression(node)) { - if (node.computed) { - computedProperties.add(node.property); - } - return getDeepProperties(node.object, [node.property, ...properties], computedProperties); - } else if (t.isIdentifier(node)) { - return [[node, ...properties], computedProperties]; - } - - throw new UnsupportedNodeTypeError(node); - } - - function buildMemberChain(properties, computedProperties) { - if (properties.length > 1) { - const lead = properties.slice(0, properties.length - 1); - const last = properties[properties.length - 1]; - return t.MemberExpression( - buildMemberChain(lead, computedProperties), - last, - computedProperties.has(last) - ); - } else if (properties.length === 1) { - return properties[0]; - } - return t.identifier('undefined'); - } - - function buildExpandedMemberNullChecks( - leadingProperties = [], - trailingProperties = [], - computedProperties - ) { - const propertyChainNullCheck = t.BinaryExpression( - '!=', - buildMemberChain(leadingProperties, computedProperties), - t.NullLiteral() - ); - - if (trailingProperties.length <= 1) { - return propertyChainNullCheck; - } - - const [headTrailingProperty, ...tailProperties] = trailingProperties; - - return t.LogicalExpression( - '&&', - propertyChainNullCheck, - buildExpandedMemberNullChecks( - [...leadingProperties, headTrailingProperty], - tailProperties, - computedProperties - ) - ); - } - - function buildExpandedMemberAccess(node, state) { - let baseNode; - let properties; - let computedProperties; - - try { - [[baseNode, ...properties], computedProperties] = getDeepProperties(node); - } catch (error) { - if (error instanceof UnsupportedNodeTypeError) { - throw state.file.buildCodeFrameError( - node, - 'idx callbacks may only access properties on the callback parameter.' - ); - } - - throw error; - } - - if (baseNode.name !== state.base.name) { - throw state.file.buildCodeFrameError( - node, - 'The parameter of the arrow function supplied to `idx` must match ' + - 'the base of the body expression.' - ); - } - return t.ConditionalExpression( - buildExpandedMemberNullChecks([state.input], properties, computedProperties), - buildMemberChain([state.input, ...properties], computedProperties), - t.identifier('undefined') - ); - } - - function visitIdxCallExpression(path, state) { - const node = path.node; - checkIdxArguments(state.file, node); - const replacement = buildExpandedMemberAccess(node.arguments[1].body, { - file: state.file, - input: node.arguments[0], - base: node.arguments[1].params[0], - }); - path.replaceWith(replacement); - } - - function isIdxImportOrRequire(node, name) { - if (t.isImportDeclaration(node)) { - if (name instanceof RegExp) { - return name.test(node.source.value); - } else { - return t.isStringLiteral(node.source, { value: name }); - } - } else if (t.isVariableDeclarator(node)) { - return ( - t.isCallExpression(node.init) && - t.isIdentifier(node.init.callee, { name: 'require' }) && - (name instanceof RegExp - ? name.test(node.init.arguments[0].value) - : t.isLiteral(node.init.arguments[0], { value: name })) - ); - } else { - return false; - } - } - - const declareVisitor = { - 'ImportDeclaration|VariableDeclarator'(path, state) { - if (!isIdxImportOrRequire(path.node, state.importName)) { - return; - } - - checkIdxBindingNode(state.file, path.node); - - const bindingName = t.isImportDeclaration(path.node) - ? path.node.specifiers[0].local.name - : path.node.id.name; - const idxBinding = path.scope.getOwnBinding(bindingName); - - idxBinding.constantViolations.forEach(refPath => { - throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.'); - }); - - let didTransform = false; - let didSkip = false; - - // Traverse the references backwards to process inner calls before - // outer calls. - idxBinding.referencePaths - .slice() - .reverse() - .forEach(refPath => { - if (refPath.node === idxBinding.node) { - // Do nothing... - } else if (refPath.parentPath.isMemberExpression()) { - visitIdxCallExpression(refPath.parentPath.parentPath, state); - didTransform = true; - } else if (refPath.parentPath.isCallExpression()) { - visitIdxCallExpression(refPath.parentPath, state); - didTransform = true; - } else { - // Should this throw? - didSkip = true; - } - }); - if (didTransform && !didSkip) { - path.remove(); - } - }, - }; - - return { - visitor: { - Program(path, state) { - const importName = state.opts.importName || '@kbn/elastic-idx'; - // If there can't reasonably be an idx call, exit fast. - if (importName !== '@kbn/elastic-idx' || idxRe.test(state.file.code)) { - // We're very strict about the shape of idx. Some transforms, like - // "babel-plugin-transform-async-to-generator", will convert arrow - // functions inside async functions into regular functions. So we do - // our transformation before any one else interferes. - const newState = { file: state.file, importName }; - path.traverse(declareVisitor, newState); - } - }, - }, - }; -}; diff --git a/packages/kbn-elastic-idx/babel/index.test.js b/packages/kbn-elastic-idx/babel/index.test.js deleted file mode 100644 index c4aee5266d6fa..0000000000000 --- a/packages/kbn-elastic-idx/babel/index.test.js +++ /dev/null @@ -1,694 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/* @notice - * This product includes code that is based on facebookincubator/idx, which was - * available under a "MIT" license. - * - * MIT License - * - * Copyright (c) 2013-present, Facebook, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -'use strict'; // eslint-disable-line strict - -jest.autoMockOff(); - -const { transformSync: babelTransform } = require('@babel/core'); -const babelPluginIdx = require('./index'); -const transformAsyncToGenerator = require('@babel/plugin-transform-async-to-generator'); -const vm = require('vm'); - -function transform(source, plugins, options) { - return babelTransform(source, { - plugins: plugins || [[babelPluginIdx, options]], - babelrc: false, - highlightCode: false, - }).code; -} - -const asyncToGeneratorHelperCode = ` -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} - -function _asyncToGenerator(fn) { - return function() { - var self = this, - args = arguments; - return new Promise(function(resolve, reject) { - var gen = fn.apply(self, args); - - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - _next(undefined); - }); - }; -} -`; - -function stringByTrimmingSpaces(string) { - return string.replace(/\s+/g, ''); -} - -expect.extend({ - toTransformInto: (input, expected) => { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - const actual = transform(code, plugins, options); - const pass = stringByTrimmingSpaces(actual) === stringByTrimmingSpaces(expected); - return { - pass, - message: () => - 'Expected input to transform into:\n' + expected + '\n' + 'Instead, got:\n' + actual, - }; - }, - toThrowTransformError: (input, expected) => { - try { - const plugins = typeof input === 'string' ? null : input.plugins; - const options = typeof input === 'string' ? undefined : input.options; - const code = typeof input === 'string' ? input : input.code; - transform(code, plugins, options); - } catch (error) { - const actual = /^.+:\s*(.*)/.exec(error.message)[1]; // Strip "undefined: " and code snippet - return { - pass: actual === expected, - message: () => - 'Expected transform to throw "' + expected + '", but instead ' + 'got "' + actual + '".', - }; - } - return { - pass: false, - message: () => 'Expected transform to throw "' + expected + '".', - }; - }, - toReturn: (input, expected) => { - const code = transform(input, undefined); - const actual = vm.runInNewContext(code); - return { - pass: actual === expected, - message: () => 'Expected "' + expected + '" but got "' + actual + '".', - }; - }, -}); - -describe('kbn-babel-plugin-apm-idx', () => { - it('transforms member expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c.d.e); - `).toTransformInto(` - base != null && base.b != null && base.b.c != null && base.b.c.d != null - ? base.b.c.d.e - : undefined; - `); - }); - - it('throws on call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b.c(...foo)().d(bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms bracket notation', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"][0][c + d]); - `).toTransformInto(` - base != null && base["b"] != null && base["b"][0] != null ? base["b"][0][c + d] : undefined; - `); - }); - - it('throws on bracket notation call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _["b"](...foo)()[0][c + d](bar, null, [...baz])); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms combination of both member access notations', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.a["b"].c[d[e[f]]].g); - `).toTransformInto(` - base != null && base.a != null && base.a["b"] != null && base.a["b"].c != null && base.a["b"].c[d[e[f]]] != null - ? base.a["b"].c[d[e[f]]].g - : undefined; - `); - }); - - it('transforms if the base is an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(this.props.base[5], _ => _.property); - `).toTransformInto(` - this.props.base[5] != null ? this.props.base[5].property : undefined; - `); - }); - - it('throws if the arrow function has more than one param', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, (a, b) => _.property); - `).toThrowTransformError( - 'The arrow function supplied to `idx` must take exactly one parameter.' - ); - }); - - it('throws if the arrow function has an invalid base', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, a => b.property) - `).toThrowTransformError( - 'The parameter of the arrow function supplied to `idx` must match the ' + - 'base of the body expression.' - ); - }); - - it('throws if the arrow function expression has non-properties/methods', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => (_.a++).b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('throws if the body of the arrow function is not an expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => {}) - `).toThrowTransformError( - 'The body of the arrow function supplied to `idx` must be a single ' + - 'expression (without curly braces).' - ); - }); - - it('ignores non-function call idx', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - result = idx; - `); - }); - - it('throws if idx is called with zero arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with one argument', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('throws if idx is called with three arguments', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(1, 2, 3); - `).toThrowTransformError('The `idx` function takes exactly two arguments.'); - }); - - it('transforms idx calls as part of another expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - paddingStatement(); - a = idx(base, _ => _.b[c]); - `).toTransformInto(` - paddingStatement(); - a = base != null && base.b != null ? base.b[c] : undefined; - `); - }); - - it('transforms nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toTransformInto(` - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ) != null - && - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e != null ? - ( - (base != null && base.a != null ? base.a.b : undefined) != null && - (base != null && base.a != null ? base.a.b : undefined).c != null ? - (base != null && base.a != null ? base.a.b : undefined).c.d : - undefined - ).e.f : - undefined; - `); - }); - - it('transforms idx calls inside async functions (plugin order #1)', () => { - expect({ - plugins: [babelPluginIdx, transformAsyncToGenerator], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls inside async functions (plugin order #2)', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - async function f() { - idx(base, _ => _.b.c.d.e); - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - function f() { - return _f.apply(this, arguments); - } - - function _f() { - _f = _asyncToGenerator(function* () { - base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined; - }); - return _f.apply(this, arguments); - } - `); - }); - - it('transforms idx calls in async methods', () => { - expect({ - plugins: [transformAsyncToGenerator, babelPluginIdx], - code: ` - import { idx } from '@kbn/elastic-idx'; - class Foo { - async bar() { - idx(base, _ => _.b); - return this; - } - } - `, - }).toTransformInto(` - ${asyncToGeneratorHelperCode} - - class Foo { - bar() { - var _this = this; - - return _asyncToGenerator(function* () { - base != null ? base.b : undefined; - return _this; - })(); - } - } - `); - }); - - it('transforms idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('transforms deep idx calls when an idx import binding is in scope', () => { - expect(` - import idx from '@kbn/elastic-idx'; - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms deep idx calls when an idx const binding is in scope', () => { - expect(` - const idx = require('@kbn/elastic-idx'); - function f() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function f() { - base != null ? base.b : undefined; - } - `); - }); - - it('transforms idx calls when an idx is called as a member function on the binding in scope', () => { - expect(` - const elastic_idx = require("@kbn/elastic-idx"); - const result = elastic_idx.idx(base, _ => _.a.b.c.d); - `).toTransformInto(` - const result = base != null && - base.a != null && - base.a.b != null && - base.a.b.c != null ? - base.a.b.c.d : - undefined; - `); - }); - - it('throws on base call expressions', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _().b.c); - `).toThrowTransformError('idx callbacks may only access properties on the callback parameter.'); - }); - - it('transforms when the idx parent is a scope creating expression', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - (() => idx(base, _ => _.b)); - `).toTransformInto(` - () => base != null ? base.b : undefined; - `); - }); - - it('throws if redefined before use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx = null; - idx(base, _ => _.b); - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if redefined after use', () => { - expect(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - idx = null; - `).toThrowTransformError('`idx` cannot be redefined.'); - }); - - it('throws if there is a duplicate declaration', () => { - expect(() => - transform(` - let idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - function idx() {} - `) - ).toThrow(); - }); - - it('handles sibling scopes with unique idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - base != null ? base.b : undefined; - } - `); - }); - - it('handles sibling scopes with and without idx', () => { - expect(` - function aaa() { - const idx = require('@kbn/elastic-idx'); - idx(base, _ => _.b); - } - function bbb() { - idx(base, _ => _.b); - } - `).toTransformInto(` - function aaa() { - base != null ? base.b : undefined; - } - function bbb() { - idx(base, _ => _.b); - } - `); - }); - - it('handles nested scopes with shadowing', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - function aaa() { - idx(base, _ => _.b); - function bbb(idx) { - idx(base, _ => _.b); - } - } - `).toTransformInto(` - base != null ? base.b : undefined; - function aaa() { - base != null ? base.b : undefined; - function bbb(idx) { - idx(base, _ => _.b); - } - } - `); - }); - - it('handles named idx import', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('throws on default plus named import', () => { - expect(` - import idx, {foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on default plus namespace import', () => { - expect(` - import idx, * as foo from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('throws on named default plus other import', () => { - expect(` - import {default as idx, foo} from '@kbn/elastic-idx'; - idx(base, _ => _.b); - `).toThrowTransformError('The idx import must be a single specifier.'); - }); - - it('unused idx import should be left alone', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - `).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - `); - }); - - it('allows configuration of the import name', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the import name', () => { - expect({ - code: ` - import { idx } from '@kbn/elastic-idx'; - import { idx as i_d_x } from 'i_d_x'; - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - import { idx } from '@kbn/elastic-idx'; - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - it('allows configuration of the require name as a string', () => { - expect({ - code: ` - import { idx } from 'i_d_x'; - idx(base, _ => _.b); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('allows configuration of the require name as a RegExp', () => { - expect({ - code: ` - import { idx } from '../../common/idx'; - idx(base, _ => _.b); - `, - options: { importName: /.*idx$/ }, - }).toTransformInto(` - base != null ? base.b : undefined; - `); - }); - - it('follows configuration of the require name', () => { - expect({ - code: ` - const idx = require('@kbn/elastic-idx'); - const i_d_x = require('i_d_x'); - i_d_x(base, _ => _.b); - idx(base, _ => _.c); - `, - options: { importName: 'i_d_x' }, - }).toTransformInto(` - const idx = require('@kbn/elastic-idx'); - - base != null ? base.b : undefined; - idx(base, _ => _.c); - `); - }); - - describe('functional', () => { - it('works with only properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: 2}}}; - idx(base, _ => _.a.b.c); - `).toReturn(2); - }); - - it('works with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {}}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with null properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: null}}; - idx(base, _ => _.a.b.c); - `).toReturn(undefined); - }); - - it('works with nested idx calls', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: {d: {e: {f: 2}}}}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(2); - }); - - it('works with nested idx calls with missing properties', () => { - expect(` - import { idx } from '@kbn/elastic-idx'; - const base = {a: {b: {c: null}}}; - idx( - idx( - idx(base, _ => _.a.b), - _ => _.c.d - ), - _ => _.e.f - ); - `).toReturn(undefined); - }); - }); -}); diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json deleted file mode 100644 index 9532983942d6b..0000000000000 --- a/packages/kbn-elastic-idx/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@kbn/elastic-idx", - "version": "1.0.0", - "private": true, - "license": "Apache-2.0", - "description": "Library for optional chaining & the Babel plugin to transpile idx calls to plain, optimized JS", - "main": "target/index.js", - "types": "target/index.d.js", - "repository": { - "type": "git", - "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-elastic-idx" - }, - "scripts": { - "build": "tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch", - "test": "jest" - }, - "devDependencies": { - "@babel/core": "^7.5.5", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "jest": "^24.9.0", - "typescript": "3.7.2" - }, - "jest": { - "testEnvironment": "node" - } -} diff --git a/packages/kbn-elastic-idx/src/index.ts b/packages/kbn-elastic-idx/src/index.ts deleted file mode 100644 index eeb0df2747a14..0000000000000 --- a/packages/kbn-elastic-idx/src/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * DeepRequiredArray - * Nested array condition handler - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface DeepRequiredArray extends Array> {} - -/** - * DeepRequiredObject - * Nested object condition handler - */ -type DeepRequiredObject = { [P in keyof T]-?: DeepRequired }; - -/** - * Function that has deeply required return type - */ -type FunctionWithRequiredReturnType any> = T extends ( - ...args: infer A -) => infer R - ? (...args: A) => DeepRequired - : never; - -/** - * DeepRequired - * Required that works for deeply nested structure - */ -type DeepRequired = NonNullable extends never - ? T - : T extends any[] - ? DeepRequiredArray - : T extends (...args: any[]) => any - ? FunctionWithRequiredReturnType - : NonNullable extends object - ? DeepRequiredObject> - : T; - -export function idx( - input: T1, - accessor: (input: NonNullable>) => T2 -): T2 | undefined { - try { - return accessor(input as NonNullable>); - } catch (error) { - return undefined; - } -} diff --git a/packages/kbn-elastic-idx/tsconfig.json b/packages/kbn-elastic-idx/tsconfig.json deleted file mode 100644 index 27833d7594031..0000000000000 --- a/packages/kbn-elastic-idx/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "./target" - }, - "include": ["src/**/*"] -} diff --git a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js index 3ff674c89682d..37ea49172d2c4 100644 --- a/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/failure_hooks/config.js @@ -29,18 +29,19 @@ export default function () { services: { hookIntoLIfecycle({ getService }) { const log = getService('log'); + const lifecycle = getService('lifecycle') - getService('lifecycle') - .on('testFailure', async (err, test) => { - log.info('testFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); - }) - .on('testHookFailure', async (err, test) => { - log.info('testHookFailure %s %s', err.message, test.fullTitle()); - await delay(10); - log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); - }); + lifecycle.testFailure.add(async (err, test) => { + log.info('testFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); + + lifecycle.testHookFailure.add(async (err, test) => { + log.info('testHookFailure %s %s', err.message, test.fullTitle()); + await delay(10); + log.info('testHookFailureAfterDelay %s %s', err.message, test.fullTitle()); + }); } }, mochaReporter: { diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 459c52997e229..fcba9691b1772 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -18,10 +18,11 @@ */ import { ToolingLog } from '@kbn/dev-utils'; -import { Suite, Test } from './fake_mocha_types'; +import { Suite, Test } from './fake_mocha_types'; import { - createLifecycle, + Lifecycle, + LifecyclePhase, readConfigFile, ProviderCollection, readProviderSpec, @@ -31,7 +32,7 @@ import { } from './lib'; export class FunctionalTestRunner { - public readonly lifecycle = createLifecycle(); + public readonly lifecycle = new Lifecycle(); private closed = false; constructor( @@ -39,13 +40,12 @@ export class FunctionalTestRunner { private readonly configFile: string, private readonly configOverrides: any ) { - this.lifecycle.on('phaseStart', name => { - log.verbose('starting %j lifecycle phase', name); - }); - - this.lifecycle.on('phaseEnd', name => { - log.verbose('ending %j lifecycle phase', name); - }); + for (const [key, value] of Object.entries(this.lifecycle)) { + if (value instanceof LifecyclePhase) { + value.before$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + value.after$.subscribe(() => log.verbose('starting %j lifecycle phase', key)); + } + } } async run() { @@ -59,7 +59,7 @@ export class FunctionalTestRunner { await providers.loadAll(); const mocha = await setupMocha(this.lifecycle, this.log, config, providers); - await this.lifecycle.trigger('beforeTests'); + await this.lifecycle.beforeTests.trigger(); this.log.info('Starting tests'); return await runTests(this.lifecycle, mocha); @@ -140,6 +140,6 @@ export class FunctionalTestRunner { if (this.closed) return; this.closed = true; - await this.lifecycle.trigger('cleanup'); + await this.lifecycle.cleanup.trigger(); } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 88995e9acc5fe..2d354938d7648 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -17,7 +17,8 @@ * under the License. */ -export { createLifecycle, Lifecycle } from './lifecycle'; +export { Lifecycle } from './lifecycle'; +export { LifecyclePhase } from './lifecycle_phase'; export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection, Provider } from './providers'; export { runTests, setupMocha } from './mocha'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 2d9629a436b3a..7f78bc28c6d3d 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -17,64 +17,22 @@ * under the License. */ -import * as Rx from 'rxjs'; - -type Listener = (...args: any[]) => Promise | void; -export type Lifecycle = ReturnType; - -export function createLifecycle() { - const listeners = { - beforeLoadTests: [] as Listener[], - beforeTests: [] as Listener[], - beforeTestSuite: [] as Listener[], - beforeEachTest: [] as Listener[], - afterTestSuite: [] as Listener[], - testFailure: [] as Listener[], - testHookFailure: [] as Listener[], - cleanup: [] as Listener[], - phaseStart: [] as Listener[], - phaseEnd: [] as Listener[], - }; - - const cleanup$ = new Rx.ReplaySubject(1); - - return { - cleanup$: cleanup$.asObservable(), - - on(name: keyof typeof listeners, fn: Listener) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - listeners[name].push(fn); - return this; - }, - - async trigger(name: keyof typeof listeners, ...args: any[]) { - if (!listeners[name]) { - throw new TypeError(`invalid lifecycle event "${name}"`); - } - - if (name === 'cleanup') { - if (cleanup$.closed) { - return; - } - - cleanup$.next(); - cleanup$.complete(); - } - - try { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseStart', name); - } - - await Promise.all(listeners[name].map(async fn => await fn(...args))); - } finally { - if (name !== 'phaseStart' && name !== 'phaseEnd') { - await this.trigger('phaseEnd', name); - } - } - }, - }; +import { LifecyclePhase } from './lifecycle_phase'; + +// mocha's global types mean we can't import Mocha or it will override the global jest types.............. +type ItsASuite = any; +type ItsATest = any; + +export class Lifecycle { + public readonly beforeTests = new LifecyclePhase<[]>({ + singular: true, + }); + public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); + public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); + public readonly testFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly testHookFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly cleanup = new LifecyclePhase<[]>({ + singular: true, + }); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts new file mode 100644 index 0000000000000..94dd76884f2ca --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.test.ts @@ -0,0 +1,206 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Rx from 'rxjs'; +import { materialize, toArray } from 'rxjs/operators'; + +import { LifecyclePhase } from './lifecycle_phase'; + +describe('with randomness', () => { + beforeEach(() => { + const randomOrder = [0, 0.75, 0.5, 0.25, 1]; + jest.spyOn(Math, 'random').mockImplementation(() => { + const n = randomOrder.shift()!; + randomOrder.push(n); + return n; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('calls handlers in random order', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + phase.add( + jest.fn(() => { + order.push('one'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('two'); + }) + ); + + phase.add( + jest.fn(() => { + order.push('three'); + }) + ); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "one", + "three", + "two", + ] + `); + }); +}); + +describe('without randomness', () => { + beforeEach(() => jest.spyOn(Math, 'random').mockImplementation(() => 0)); + afterEach(() => jest.restoreAllMocks()); + + it('calls all handlers and throws first error', async () => { + const phase = new LifecyclePhase(); + const fn1 = jest.fn(); + phase.add(fn1); + + const fn2 = jest.fn(() => { + throw new Error('foo'); + }); + phase.add(fn2); + + const fn3 = jest.fn(); + phase.add(fn3); + + await expect(phase.trigger()).rejects.toThrowErrorMatchingInlineSnapshot(`"foo"`); + expect(fn1).toHaveBeenCalled(); + expect(fn2).toHaveBeenCalled(); + expect(fn3).toHaveBeenCalled(); + }); + + it('triggers before$ just before calling handler and after$ once it resolves', async () => { + const phase = new LifecyclePhase(); + const order: string[] = []; + + const beforeSub = jest.fn(() => order.push('before')); + phase.before$.subscribe(beforeSub); + + const afterSub = jest.fn(() => order.push('after')); + phase.after$.subscribe(afterSub); + + const handler = jest.fn(async () => { + order.push('handler start'); + await new Promise(resolve => setTimeout(resolve, 100)); + order.push('handler done'); + }); + phase.add(handler); + + await phase.trigger(); + expect(order).toMatchInlineSnapshot(` + Array [ + "before", + "handler start", + "handler done", + "after", + ] + `); + }); + + it('completes before$ and after$ if phase is singular', async () => { + const phase = new LifecyclePhase({ singular: true }); + + const beforeNotifs: Array> = []; + phase.before$.pipe(materialize()).subscribe(n => beforeNotifs.push(n)); + + const afterNotifs: Array> = []; + phase.after$.pipe(materialize()).subscribe(n => afterNotifs.push(n)); + + await phase.trigger(); + expect(beforeNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + expect(afterNotifs).toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('completes before$ subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.before$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); + + it('replays after$ event subscribers after trigger of singular phase', async () => { + const phase = new LifecyclePhase({ singular: true }); + await phase.trigger(); + + await expect(phase.after$.pipe(materialize(), toArray()).toPromise()).resolves + .toMatchInlineSnapshot(` + Array [ + Notification { + "error": undefined, + "hasValue": true, + "kind": "N", + "value": undefined, + }, + Notification { + "error": undefined, + "hasValue": false, + "kind": "C", + "value": undefined, + }, + ] + `); + }); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts new file mode 100644 index 0000000000000..5c7fdb532faa1 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle_phase.ts @@ -0,0 +1,82 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Rx from 'rxjs'; + +const shuffle = (arr: T[]) => arr.slice().sort(() => (Math.random() > 0.5 ? 1 : -1)); + +export type GetArgsType> = T extends LifecyclePhase + ? X + : never; + +export class LifecyclePhase { + private readonly handlers: Array<(...args: Args) => Promise | void> = []; + + private readonly beforeSubj = new Rx.Subject(); + public readonly before$ = this.beforeSubj.asObservable(); + + private readonly afterSubj = this.options.singular + ? new Rx.ReplaySubject(1) + : new Rx.Subject(); + public readonly after$ = this.afterSubj.asObservable(); + + constructor( + private readonly options: { + singular?: boolean; + } = {} + ) {} + + public add(fn: (...args: Args) => Promise | void) { + this.handlers.push(fn); + } + + public async trigger(...args: Args) { + if (this.beforeSubj.isStopped) { + throw new Error(`singular lifecycle event can only be triggered once`); + } + + this.beforeSubj.next(undefined); + if (this.options.singular) { + this.beforeSubj.complete(); + } + + // catch the first error but still execute all handlers + let error; + + // shuffle the handlers to prevent relying on their order + for (const fn of shuffle(this.handlers)) { + try { + await fn(...args); + } catch (_error) { + if (!error) { + error = _error; + } + } + } + + this.afterSubj.next(undefined); + if (this.options.singular) { + this.afterSubj.complete(); + } + + if (error) { + throw error; + } + } +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js index e65eb2f27d063..4eb45229c2234 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/decorate_mocha_ui.js @@ -58,7 +58,7 @@ export function decorateMochaUi(lifecycle, context) { argumentsList[1] = function() { before(async () => { - await lifecycle.trigger('beforeTestSuite', this); + await lifecycle.beforeTestSuite.trigger(this); }); this.tags = tags => { @@ -68,7 +68,7 @@ export function decorateMochaUi(lifecycle, context) { provider.call(this); after(async () => { - await lifecycle.trigger('afterTestSuite', this); + await lifecycle.afterTestSuite.trigger(this); }); }; @@ -94,7 +94,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testFailure', err, test); + await lifecycle.testFailure.trigger(err, test); }) ); } @@ -112,7 +112,7 @@ export function decorateMochaUi(lifecycle, context) { return wrapNonSuiteFunction( name, wrapRunnableArgsWithErrorHandler(fn, async (err, test) => { - await lifecycle.trigger('testHookFailure', err, test); + await lifecycle.testHookFailure.trigger(err, test); }) ); } diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts index 2d98052b1062a..654f588fda858 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/run_tests.ts @@ -35,7 +35,7 @@ export async function runTests(lifecycle: Lifecycle, mocha: Mocha) { runComplete = true; }); - lifecycle.on('cleanup', () => { + lifecycle.cleanup.add(() => { if (!runComplete) runner.abort(); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index a425251a29f36..326877919d985 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -41,7 +41,7 @@ export async function setupMocha(lifecycle, log, config, providers) { // global beforeEach hook in root suite triggers before all others mocha.suite.beforeEach('global before each', async function() { - await lifecycle.trigger('beforeEachTest', this.currentTest); + await lifecycle.beforeEachTest.trigger(this.currentTest); }); loadTestFiles({ diff --git a/packages/kbn-ui-framework/dist/kui_dark.css b/packages/kbn-ui-framework/dist/kui_dark.css index dcbd65fbca520..aa16bcdb34d36 100644 --- a/packages/kbn-ui-framework/dist/kui_dark.css +++ b/packages/kbn-ui-framework/dist/kui_dark.css @@ -1,10 +1,24 @@ +/* 1 */ +/* 1 */ +/** + * 1. Extend beta badges to at least 40% of the container's width + * 2. Fix for IE to ensure badges are visible outside of a -

Cycle Slides -

+
-

Cycle Slides -

+

-

Cycle Slides -

+

-

Hide Toolbar -

+
-

Hide Toolbar -

+
-

Hide Toolbar -

+
can navigate Autoplay Settings 2`] = ` -

Cycle Slides -

+

can navigate Autoplay Settings 2`] = ` >
can navigate Autoplay Settings 2`] = ` >
can navigate Toolbar Settings, closes when activated 2`] = >
can navigate Toolbar Settings, closes when activated 2`] = -

Hide Toolbar -

+
can navigate Toolbar Settings, closes when activated 2`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings

Hide Toolbar

Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts index 9fce720f5b14b..0c593bec1af3a 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts @@ -11,7 +11,6 @@ * crashing due to the requirement of the `window` object. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { ReactNode, FunctionComponent } from 'react'; @@ -32,7 +31,7 @@ const layouts: Layouts = { }; export const findLayout = (type: InventoryItemType) => { - const Layout = idx(layouts, _ => _[type]); + const Layout = layouts?.[type]; if (!Layout) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findLayout.error', { diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts index 661b9c7a8841e..dc3c409ac497e 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx/target'; import { ReactNode, FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { InventoryItemType } from './types'; @@ -24,7 +23,7 @@ const toolbars: Toolbars = { }; export const findToolbar = (type: InventoryItemType) => { - const Toolbar = idx(toolbars, _ => _[type]); + const Toolbar = toolbars?.[type]; if (!Toolbar) { throw new Error( i18n.translate('xpack.infra.inventoryModels.findToolbar.error', { diff --git a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx index cd28114327b6d..41479cf6351ec 100644 --- a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx @@ -21,6 +21,7 @@ import { InfraFrontendLibs } from '../lib/lib'; import { PageRouter } from '../routes'; import { createStore } from '../store'; import { ApolloClientContext } from '../utils/apollo_context'; +import { ReduxStateContextProvider } from '../utils/redux_context'; import { HistoryContext } from '../utils/history_context'; import { useUiSetting$, @@ -46,15 +47,17 @@ export async function startApp(libs: InfraFrontendLibs) { - - - - - - - - - + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx index 549ca4c1ae047..4cefbea7225ec 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx @@ -18,7 +18,7 @@ interface LogTextStreamLoadingItemViewProps { hasMore: boolean; isLoading: boolean; isStreaming: boolean; - lastStreamingUpdate: number | null; + lastStreamingUpdate: Date | null; onLoadMore?: () => void; } diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 674c3f59ce957..a5b85788fdea9 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -39,7 +39,7 @@ interface ScrollableLogTextStreamViewProps { hasMoreBeforeStart: boolean; hasMoreAfterEnd: boolean; isStreaming: boolean; - lastLoadedTime: number | null; + lastLoadedTime: Date | null; target: TimeKey | null; jumpToTarget: (target: TimeKey) => any; reportVisibleInterval: (params: { @@ -143,7 +143,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< const hasItems = items.length > 0; return ( - {isReloading && !hasItems ? ( + {isReloading && (!isStreaming || !hasItems) ? ( extends React.PureComponent< // Flag the scrollTop change that's about to happen as programmatic, as // opposed to being in direct response to user input this.nextScrollEventFromCenterTarget = true; - scrollRef.current.scrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; - return true; + const currentScrollTop = scrollRef.current.scrollTop; + const newScrollTop = targetDimensions.top + targetOffset - scrollViewHeight / 2; + scrollRef.current.scrollTop = newScrollTop; + return currentScrollTop !== newScrollTop; } return false; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts new file mode 100644 index 0000000000000..83bae37c348d4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/gql_queries.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ApolloClient } from 'apollo-client'; +import { TimeKey } from '../../../../common/time'; +import { logEntriesQuery } from '../../../graphql/log_entries.gql_query'; +import { useApolloClient } from '../../../utils/apollo_context'; +import { LogEntriesResponse } from '.'; + +const LOAD_CHUNK_SIZE = 200; + +type LogEntriesGetter = ( + client: ApolloClient<{}>, + countBefore: number, + countAfter: number +) => (params: { + sourceId: string; + timeKey: TimeKey | null; + filterQuery: string | null; +}) => Promise; + +const getLogEntries: LogEntriesGetter = (client, countBefore, countAfter) => async ({ + sourceId, + timeKey, + filterQuery, +}) => { + if (!timeKey) throw new Error('TimeKey is null'); + const result = await client.query({ + query: logEntriesQuery, + variables: { + sourceId, + timeKey: { time: timeKey.time, tiebreaker: timeKey.tiebreaker }, + countBefore, + countAfter, + filterQuery, + }, + fetchPolicy: 'no-cache', + }); + // Workaround for Typescript. Since we're removing the GraphQL API in another PR or two + // 7.6 goes out I don't think it's worth the effort to actually make this + // typecheck pass + const { source } = result.data as any; + const { logEntriesAround } = source; + return { + entries: logEntriesAround.entries, + entriesStart: logEntriesAround.start, + entriesEnd: logEntriesAround.end, + hasMoreAfterEnd: logEntriesAround.hasMoreAfter, + hasMoreBeforeStart: logEntriesAround.hasMoreBefore, + lastLoadedTime: new Date(), + }; +}; + +export const useGraphQLQueries = () => { + const client = useApolloClient(); + if (!client) throw new Error('Unable to get Apollo Client from context'); + return { + getLogEntriesAround: getLogEntries(client, LOAD_CHUNK_SIZE, LOAD_CHUNK_SIZE), + getLogEntriesBefore: getLogEntries(client, LOAD_CHUNK_SIZE, 0), + getLogEntriesAfter: getLogEntries(client, 0, LOAD_CHUNK_SIZE), + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts new file mode 100644 index 0000000000000..3020ad7eb5f84 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -0,0 +1,268 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { useEffect, useState, useReducer, useCallback } from 'react'; +import createContainer from 'constate'; +import { pick, throttle } from 'lodash'; +import { useGraphQLQueries } from './gql_queries'; +import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; +import { InfraLogEntry } from './types'; + +const DESIRED_BUFFER_PAGES = 2; + +enum Action { + FetchingNewEntries, + FetchingMoreEntries, + ReceiveNewEntries, + ReceiveEntriesBefore, + ReceiveEntriesAfter, + ErrorOnNewEntries, + ErrorOnMoreEntries, +} + +type ReceiveActions = + | Action.ReceiveNewEntries + | Action.ReceiveEntriesBefore + | Action.ReceiveEntriesAfter; + +interface ReceiveEntriesAction { + type: ReceiveActions; + payload: LogEntriesResponse; +} +interface FetchOrErrorAction { + type: Exclude; +} +type ActionObj = ReceiveEntriesAction | FetchOrErrorAction; + +type Dispatch = (action: ActionObj) => void; + +interface LogEntriesProps { + filterQuery: string | null; + timeKey: TimeKey | null; + pagesBeforeStart: number | null; + pagesAfterEnd: number | null; + sourceId: string; + isAutoReloading: boolean; +} + +type FetchEntriesParams = Omit; +type FetchMoreEntriesParams = Pick; + +export interface LogEntriesResponse { + entries: InfraLogEntry[]; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + hasMoreAfterEnd: boolean; + hasMoreBeforeStart: boolean; + lastLoadedTime: Date | null; +} + +export type LogEntriesStateParams = { + isReloading: boolean; + isLoadingMore: boolean; +} & LogEntriesResponse; + +export interface LogEntriesCallbacks { + fetchNewerEntries: () => Promise; +} +export const logEntriesInitialCallbacks = { + fetchNewerEntries: async () => {}, +}; + +export const logEntriesInitialState: LogEntriesStateParams = { + entries: [], + entriesStart: null, + entriesEnd: null, + hasMoreAfterEnd: false, + hasMoreBeforeStart: false, + isReloading: true, + isLoadingMore: false, + lastLoadedTime: null, +}; + +const cleanDuplicateItems = (entriesA: InfraLogEntry[], entriesB: InfraLogEntry[]) => { + const gids = new Set(entriesB.map(item => item.gid)); + return entriesA.filter(item => !gids.has(item.gid)); +}; + +const shouldFetchNewEntries = ({ + prevParams, + timeKey, + filterQuery, + entriesStart, + entriesEnd, +}: FetchEntriesParams & LogEntriesStateParams & { prevParams: FetchEntriesParams }) => { + if (!timeKey) return false; + const shouldLoadWithNewFilter = filterQuery !== prevParams.filterQuery; + const shouldLoadAroundNewPosition = + !entriesStart || !entriesEnd || !timeKeyIsBetween(entriesStart, entriesEnd, timeKey); + return shouldLoadWithNewFilter || shouldLoadAroundNewPosition; +}; + +enum ShouldFetchMoreEntries { + Before, + After, +} + +const shouldFetchMoreEntries = ( + { pagesAfterEnd, pagesBeforeStart }: FetchMoreEntriesParams, + { hasMoreBeforeStart, hasMoreAfterEnd }: LogEntriesStateParams +) => { + if (pagesBeforeStart === null || pagesAfterEnd === null) return false; + if (pagesBeforeStart < DESIRED_BUFFER_PAGES && hasMoreBeforeStart) + return ShouldFetchMoreEntries.Before; + if (pagesAfterEnd < DESIRED_BUFFER_PAGES && hasMoreAfterEnd) return ShouldFetchMoreEntries.After; + return false; +}; + +const useFetchEntriesEffect = ( + state: LogEntriesStateParams, + dispatch: Dispatch, + props: LogEntriesProps +) => { + const { getLogEntriesAround, getLogEntriesBefore, getLogEntriesAfter } = useGraphQLQueries(); + + const [prevParams, cachePrevParams] = useState(props); + const [startedStreaming, setStartedStreaming] = useState(false); + + const runFetchNewEntriesRequest = async () => { + dispatch({ type: Action.FetchingNewEntries }); + try { + const payload = await getLogEntriesAround(props); + dispatch({ type: Action.ReceiveNewEntries, payload }); + } catch (e) { + dispatch({ type: Action.ErrorOnNewEntries }); + } + }; + + const runFetchMoreEntriesRequest = async (direction: ShouldFetchMoreEntries) => { + dispatch({ type: Action.FetchingMoreEntries }); + const getEntriesBefore = direction === ShouldFetchMoreEntries.Before; + const timeKey = getEntriesBefore + ? state.entries[0].key + : state.entries[state.entries.length - 1].key; + const getMoreLogEntries = getEntriesBefore ? getLogEntriesBefore : getLogEntriesAfter; + try { + const payload = await getMoreLogEntries({ ...props, timeKey }); + dispatch({ + type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, + payload, + }); + } catch (e) { + dispatch({ type: Action.ErrorOnMoreEntries }); + } + }; + + const fetchNewEntriesEffectDependencies = Object.values( + pick(props, ['sourceId', 'filterQuery', 'timeKey']) + ); + const fetchNewEntriesEffect = () => { + if (props.isAutoReloading) return; + if (shouldFetchNewEntries({ ...props, ...state, prevParams })) { + runFetchNewEntriesRequest(); + } + cachePrevParams(props); + }; + + const fetchMoreEntriesEffectDependencies = [ + ...Object.values(pick(props, ['pagesAfterEnd', 'pagesBeforeStart'])), + Object.values(pick(state, ['hasMoreBeforeStart', 'hasMoreAfterEnd'])), + ]; + const fetchMoreEntriesEffect = () => { + if (state.isLoadingMore || props.isAutoReloading) return; + const direction = shouldFetchMoreEntries(props, state); + switch (direction) { + case ShouldFetchMoreEntries.Before: + case ShouldFetchMoreEntries.After: + runFetchMoreEntriesRequest(direction); + break; + default: + break; + } + }; + + const fetchNewerEntries = useCallback( + throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), + [props] + ); + + const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore]; + const streamEntriesEffect = () => { + (async () => { + if (props.isAutoReloading && !state.isLoadingMore) { + if (startedStreaming) { + await new Promise(res => setTimeout(res, 5000)); + } else { + setStartedStreaming(true); + } + fetchNewerEntries(); + } else if (!props.isAutoReloading) { + setStartedStreaming(false); + } + })(); + }; + + useEffect(fetchNewEntriesEffect, fetchNewEntriesEffectDependencies); + useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); + useEffect(streamEntriesEffect, streamEntriesEffectDependencies); + + return { fetchNewerEntries }; +}; + +export const useLogEntriesState: ( + props: LogEntriesProps +) => [LogEntriesStateParams, LogEntriesCallbacks] = props => { + const [state, dispatch] = useReducer(logEntriesStateReducer, logEntriesInitialState); + + const { fetchNewerEntries } = useFetchEntriesEffect(state, dispatch, props); + const callbacks = { fetchNewerEntries }; + + return [state, callbacks]; +}; + +const logEntriesStateReducer = (prevState: LogEntriesStateParams, action: ActionObj) => { + switch (action.type) { + case Action.ReceiveNewEntries: + return { ...prevState, ...action.payload, isReloading: false }; + case Action.ReceiveEntriesBefore: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...action.payload.entries, ...prevEntries]; + const { hasMoreBeforeStart, entriesStart, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreBeforeStart, + entriesStart, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.ReceiveEntriesAfter: { + const prevEntries = cleanDuplicateItems(prevState.entries, action.payload.entries); + const newEntries = [...prevEntries, ...action.payload.entries]; + const { hasMoreAfterEnd, entriesEnd, lastLoadedTime } = action.payload; + const update = { + entries: newEntries, + isLoadingMore: false, + hasMoreAfterEnd, + entriesEnd, + lastLoadedTime, + }; + return { ...prevState, ...update }; + } + case Action.FetchingNewEntries: + return { ...prevState, isReloading: true }; + case Action.FetchingMoreEntries: + return { ...prevState, isLoadingMore: true }; + case Action.ErrorOnNewEntries: + return { ...prevState, isReloading: false }; + case Action.ErrorOnMoreEntries: + return { ...prevState, isLoadingMore: false }; + default: + throw new Error(); + } +}; + +export const LogEntriesState = createContainer(useLogEntriesState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts new file mode 100644 index 0000000000000..75aea8c415eee --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/types.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** A segment of the log entry message that was derived from a field */ +export interface InfraLogMessageFieldSegment { + /** The field the segment was derived from */ + field: string; + /** The segment's message */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} +/** A segment of the log entry message that was derived from a string literal */ +export interface InfraLogMessageConstantSegment { + /** The segment's message */ + constant: string; +} + +export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; + +/** A special built-in column that contains the log entry's timestamp */ +export interface InfraLogEntryTimestampColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The timestamp */ + timestamp: number; +} +/** A special built-in column that contains the log entry's constructed message */ +export interface InfraLogEntryMessageColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** A list of the formatted log entry segments */ + message: InfraLogMessageSegment[]; +} + +/** A column that contains the value of a field of the log entry */ +export interface InfraLogEntryFieldColumn { + /** The id of the corresponding column configuration */ + columnId: string; + /** The field name of the column */ + field: string; + /** The value of the field in the log entry */ + value: string; + /** A list of highlighted substrings of the value */ + highlights: string[]; +} + +/** A column of a log entry */ +export type InfraLogEntryColumn = + | InfraLogEntryTimestampColumn + | InfraLogEntryMessageColumn + | InfraLogEntryFieldColumn; + +/** A representation of the log entry's position in the event stream */ +export interface InfraTimeKey { + /** The timestamp of the event that the log entry corresponds to */ + time: number; + /** The tiebreaker that disambiguates events with the same timestamp */ + tiebreaker: number; +} + +/** A log entry */ +export interface InfraLogEntry { + /** A unique representation of the log entry's position in the event stream */ + key: InfraTimeKey; + /** The log entry's id */ + gid: string; + /** The source id */ + source: string; + /** The columns used for rendering the log entry */ + columns: InfraLogEntryColumn[]; +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts new file mode 100644 index 0000000000000..a737d19a5923d --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logFilterSelectors as logFilterReduxSelectors } from '../../../store/local/selectors'; + +export const useLogFilterState = () => { + const { local: state } = useContext(ReduxStateContext); + const filterQuery = logFilterReduxSelectors.selectLogFilterQueryAsJson(state); + return { filterQuery }; +}; + +export interface LogFilterStateParams { + filterQuery: string | null; +} + +export const logFilterInitialState = { + filterQuery: null, +}; + +export const LogFilterState = createContainer(useLogFilterState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index 39c088b1ceb5a..fa1ccb4efa4bb 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -6,30 +6,30 @@ import createContainer from 'constate'; import { useState, useContext } from 'react'; - import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; import { useReduxBridgeSetters } from './redux_bridge_setters'; import { useLogSummaryBufferInterval } from '../log_summary'; import { LogViewConfiguration } from '../log_view_configuration'; +import { TimeKey } from '../../../../common/time'; export const useLogHighlightsState = ({ sourceId, sourceVersion, + entriesStart, + entriesEnd, + filterQuery, }: { sourceId: string; sourceVersion: string | undefined; + entriesStart: TimeKey | null; + entriesEnd: TimeKey | null; + filterQuery: string | null; }) => { const [highlightTerms, setHighlightTerms] = useState([]); - const { - startKey, - endKey, - filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, @@ -50,7 +50,14 @@ export const useLogHighlightsState = ({ logEntryHighlights, logEntryHighlightsById, loadLogEntryHighlightsRequest, - } = useLogEntryHighlights(sourceId, sourceVersion, startKey, endKey, filterQuery, highlightTerms); + } = useLogEntryHighlights( + sourceId, + sourceVersion, + entriesStart, + entriesEnd, + filterQuery, + highlightTerms + ); const { logSummaryHighlights, loadLogSummaryHighlightsRequest } = useLogSummaryHighlights( sourceId, @@ -78,8 +85,6 @@ export const useLogHighlightsState = ({ return { highlightTerms, setHighlightTerms, - setStartKey, - setEndKey, setFilterQuery, logEntryHighlights, logEntryHighlightsById, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx index b3254f597dfcf..0e778f35188f0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx @@ -8,19 +8,13 @@ import { useState } from 'react'; import { TimeKey } from '../../../../common/time'; export const useReduxBridgeSetters = () => { - const [startKey, setStartKey] = useState(null); - const [endKey, setEndKey] = useState(null); const [filterQuery, setFilterQuery] = useState(null); const [visibleMidpoint, setVisibleMidpoint] = useState(null); const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined); return { - startKey, - endKey, filterQuery, visibleMidpoint, - setStartKey, - setEndKey, setFilterQuery, setVisibleMidpoint, jumpToTarget, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx index 220eaade12fa6..2b60c6edd97aa 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx @@ -8,23 +8,11 @@ import React, { useEffect, useContext } from 'react'; import { TimeKey } from '../../../../common/time'; import { withLogFilter } from '../with_log_filter'; -import { withStreamItems } from '../with_stream_items'; import { withLogPosition } from '../with_log_position'; import { LogHighlightsState } from './log_highlights'; // Bridges Redux container state with Hooks state. Once state is moved fully from // Redux to Hooks this can be removed. -export const LogHighlightsStreamItemsBridge = withStreamItems( - ({ entriesStart, entriesEnd }: { entriesStart: TimeKey | null; entriesEnd: TimeKey | null }) => { - const { setStartKey, setEndKey } = useContext(LogHighlightsState.Context); - useEffect(() => { - setStartKey(entriesStart); - setEndKey(entriesEnd); - }, [entriesStart, entriesEnd]); - - return null; - } -); export const LogHighlightsPositionBridge = withLogPosition( ({ @@ -61,7 +49,6 @@ export const LogHighlightsFilterQueryBridge = withLogFilter( export const LogHighlightsBridge = ({ indexPattern }: { indexPattern: any }) => ( <> - diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts new file mode 100644 index 0000000000000..7cc8050aafd14 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useContext } from 'react'; +import createContainer from 'constate'; +import { ReduxStateContext } from '../../../utils/redux_context'; +import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors'; +import { TimeKey } from '../../../../common/time'; + +export const useLogPositionState = () => { + const { local: state } = useContext(ReduxStateContext); + const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state); + const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state); + const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state); + return { timeKey, isAutoReloading, ...pages }; +}; + +export interface LogPositionStateParams { + timeKey: TimeKey | null; + pagesAfterEnd: number | null; + pagesBeforeStart: number | null; + isAutoReloading: boolean; +} + +export const logPositionInitialState = { + timeKey: null, + pagesAfterEnd: null, + pagesBeforeStart: null, + isAutoReloading: false, +}; + +export const LogPositionState = createContainer(useLogPositionState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 12117d88f8283..da468b4391e4e 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -4,85 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useContext, useMemo } from 'react'; -import { connect } from 'react-redux'; - +import { useContext, useMemo } from 'react'; import { StreamItem, LogEntryStreamItem } from '../../components/logging/log_text_stream/item'; -import { logEntriesActions, logEntriesSelectors, logPositionSelectors, State } from '../../store'; import { LogEntry, LogEntryHighlight } from '../../utils/log_entry'; -import { PropsOfContainer, RendererFunction } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; +import { RendererFunction } from '../../utils/typed_react'; // deep inporting to avoid a circular import problem import { LogHighlightsState } from './log_highlights/log_highlights'; +import { LogPositionState } from './log_position'; +import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; -export const withStreamItems = connect( - (state: State) => ({ - isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), - isReloading: logEntriesSelectors.selectIsReloadingEntries(state), - isLoadingMore: logEntriesSelectors.selectIsLoadingMoreEntries(state), - wasAutoReloadJustAborted: logPositionSelectors.selectAutoReloadJustAborted(state), - hasMoreBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart(state), - hasMoreAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd(state), - lastLoadedTime: logEntriesSelectors.selectEntriesLastLoadedTime(state), - entries: logEntriesSelectors.selectEntries(state), - entriesStart: logEntriesSelectors.selectEntriesStart(state), - entriesEnd: logEntriesSelectors.selectEntriesEnd(state), - }), - bindPlainActionCreators({ - loadNewerEntries: logEntriesActions.loadNewerEntries, - reloadEntries: logEntriesActions.reloadEntries, - setSourceId: logEntriesActions.setSourceId, - }) -); - -type WithStreamItemsProps = PropsOfContainer; - -export const WithStreamItems = withStreamItems( - ({ - children, - initializeOnMount, - ...props - }: WithStreamItemsProps & { - children: RendererFunction< - WithStreamItemsProps & { +export const WithStreamItems: React.FunctionComponent<{ + children: RendererFunction< + LogEntriesStateParams & + LogEntriesCallbacks & { currentHighlightKey: UniqueTimeKey | null; items: StreamItem[]; } - >; - initializeOnMount: boolean; - }) => { - const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); - const items = useMemo( - () => - props.isReloading && !props.isAutoReloading && !props.wasAutoReloadJustAborted - ? [] - : props.entries.map(logEntry => - createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) - ), - - [ - props.isReloading, - props.isAutoReloading, - props.wasAutoReloadJustAborted, - props.entries, - logEntryHighlightsById, - ] - ); - - useEffect(() => { - if (initializeOnMount && !props.isReloading && !props.isLoadingMore) { - props.reloadEntries(); - } - }, []); - - return children({ - ...props, - currentHighlightKey, - items, - }); - } -); + >; +}> = ({ children }) => { + const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context); + const { isAutoReloading } = useContext(LogPositionState.Context); + const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); + + const items = useMemo( + () => + logEntries.isReloading && !isAutoReloading + ? [] + : logEntries.entries.map(logEntry => + createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) + ), + + [logEntries.entries, logEntryHighlightsById] + ); + + return children({ + ...logEntries, + ...logEntriesCallbacks, + items, + currentHighlightKey, + }); +}; const createLogEntryStreamItem = ( logEntry: LogEntry, @@ -92,23 +54,3 @@ const createLogEntryStreamItem = ( logEntry, highlights, }); - -/** - * This component serves as connection between the state and side-effects - * managed by redux and the state and effects managed by hooks. In particular, - * it forwards changes of the source id to redux via the action creator - * `setSourceId`. - * - * It will be mounted beneath the hierachy level where the redux store and the - * source state are initialized. Once the log entry state and loading - * side-effects have been migrated from redux to hooks it can be removed. - */ -export const ReduxSourceIdBridge = withStreamItems( - ({ setSourceId, sourceId }: { setSourceId: (sourceId: string) => void; sourceId: string }) => { - useEffect(() => { - setSourceId(sourceId); - }, [setSourceId, sourceId]); - - return null; - } -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts similarity index 93% rename from x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts rename to x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts index 78893b83cd645..41ff3c293a713 100644 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/log_entries.gql_query.ts +++ b/x-pack/legacy/plugins/infra/public/graphql/log_entries.gql_query.ts @@ -6,7 +6,7 @@ import gql from 'graphql-tag'; -import { sharedFragments } from '../../../../../common/graphql/shared'; +import { sharedFragments } from '../../common/graphql/shared'; export const logEntriesQuery = gql` query LogEntries( diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx index a54780267f1c1..a7dff2f11f232 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_http_request.tsx @@ -8,7 +8,6 @@ import React, { useMemo, useState } from 'react'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx/target'; import { KFetchError } from 'ui/kfetch/kfetch_error'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { useTrackedPromise } from '../utils/use_tracked_promise'; @@ -44,13 +43,13 @@ export function useHTTPRequest( defaultMessage: `Error`, })} - {idx(err.res, r => r.statusText)} ({idx(err.res, r => r.status)}) + {err.res?.statusText} ({err.res?.status})
{i18n.translate('xpack.infra.useHTTPRequest.error.url', { defaultMessage: `URL`, })}
- {idx(err.res, r => r.url)} + {err.res?.url}
), }); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index beb5eb391d368..88212849d4594 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -24,7 +24,7 @@ import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minima import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position'; import { WithLogPosition } from '../../../containers/logs/with_log_position'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; -import { ReduxSourceIdBridge, WithStreamItems } from '../../../containers/logs/with_stream_items'; +import { WithStreamItems } from '../../../containers/logs/with_stream_items'; import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; @@ -44,10 +44,8 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); const derivedIndexPattern = createDerivedIndexPattern('logs'); - return ( <> - @@ -87,7 +85,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { scrollUnlockLiveStreaming, isScrollLocked, }) => ( - + {({ currentHighlightKey, hasMoreAfterEnd, @@ -96,7 +94,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { isReloading, items, lastLoadedTime, - loadNewerEntries, + fetchNewerEntries, }) => ( { items={items} jumpToTarget={jumpToTargetPosition} lastLoadedTime={lastLoadedTime} - loadNewerItems={loadNewerEntries} + loadNewerItems={fetchNewerEntries} reportVisibleInterval={reportVisiblePositions} scale={textScale} target={targetPosition} @@ -140,7 +138,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { visibleMidpointTime, visibleTimeInterval, }) => ( - + {({ isReloading }) => ( { +const LogEntriesStateProvider: React.FC = ({ children }) => { + const { sourceId } = useContext(Source.Context); + const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( + LogPositionState.Context + ); + const { filterQuery } = useContext(LogFilterState.Context); + const entriesProps = { + timeKey, + pagesBeforeStart, + pagesAfterEnd, + filterQuery, + sourceId, + isAutoReloading, + }; + return {children}; +}; + +const LogHighlightsStateProvider: React.FC = ({ children }) => { const { sourceId, version } = useContext(Source.Context); + const [{ entriesStart, entriesEnd }] = useContext(LogEntriesState.Context); + const { filterQuery } = useContext(LogFilterState.Context); + const highlightsProps = { + sourceId, + sourceVersion: version, + entriesStart, + entriesEnd, + filterQuery, + }; + return {children}; +}; +export const LogsPageProviders: React.FunctionComponent = ({ children }) => { return ( - - {children} - + + + + {children} + + + ); diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index 70855101518d6..e2be0d64b8f1e 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -11,4 +11,3 @@ export { waffleTimeActions, waffleOptionsActions, } from './local'; -export { logEntriesActions } from './remote'; diff --git a/x-pack/legacy/plugins/infra/public/store/epics.ts b/x-pack/legacy/plugins/infra/public/store/epics.ts index 4df8e1368ca01..b5e48a4ec6214 100644 --- a/x-pack/legacy/plugins/infra/public/store/epics.ts +++ b/x-pack/legacy/plugins/infra/public/store/epics.ts @@ -7,7 +7,5 @@ import { combineEpics } from 'redux-observable'; import { createLocalEpic } from './local'; -import { createRemoteEpic } from './remote'; -export const createRootEpic = () => - combineEpics(createLocalEpic(), createRemoteEpic()); +export const createRootEpic = () => combineEpics(createLocalEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/epic.ts index 4cfac85f00b15..e1a051355576f 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/epic.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/epic.ts @@ -6,8 +6,6 @@ import { combineEpics } from 'redux-observable'; -import { createLogPositionEpic } from './log_position'; import { createWaffleTimeEpic } from './waffle_time'; -export const createLocalEpic = () => - combineEpics(createLogPositionEpic(), createWaffleTimeEpic()); +export const createLocalEpic = () => combineEpics(createWaffleTimeEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts index 17e06348d18b2..3edb289985d55 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts @@ -8,5 +8,4 @@ import * as logPositionActions from './actions'; import * as logPositionSelectors from './selectors'; export { logPositionActions, logPositionSelectors }; -export * from './epic'; export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts index 3b99e2d4f4379..2ca8be8e40d86 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts @@ -17,8 +17,6 @@ import { unlockAutoReloadScroll, } from './actions'; -import { loadEntriesActionCreators } from '../../remote/log_entries/operations/load'; - interface ManualTargetPositionUpdatePolicy { policy: 'manual'; } @@ -38,9 +36,10 @@ export interface LogPositionState { startKey: TimeKey | null; middleKey: TimeKey | null; endKey: TimeKey | null; + pagesAfterEnd: number; + pagesBeforeStart: number; }; controlsShouldDisplayTargetPosition: boolean; - autoReloadJustAborted: boolean; autoReloadScrollLock: boolean; } @@ -53,9 +52,10 @@ export const initialLogPositionState: LogPositionState = { endKey: null, middleKey: null, startKey: null, + pagesBeforeStart: Infinity, + pagesAfterEnd: Infinity, }, controlsShouldDisplayTargetPosition: false, - autoReloadJustAborted: false, autoReloadScrollLock: false, }; @@ -76,11 +76,16 @@ const targetPositionUpdatePolicyReducer = reducerWithInitialState( const visiblePositionReducer = reducerWithInitialState( initialLogPositionState.visiblePositions -).case(reportVisiblePositions, (state, { startKey, middleKey, endKey }) => ({ - endKey, - middleKey, - startKey, -})); +).case( + reportVisiblePositions, + (state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({ + endKey, + middleKey, + startKey, + pagesBeforeStart, + pagesAfterEnd, + }) +); // Determines whether to use the target position or the visible midpoint when // displaying a timestamp or time range in the toolbar and log minimap. When the @@ -98,17 +103,6 @@ const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( return state; }); -// If auto reload is aborted before a pending request finishes, this flag will -// prevent the UI from displaying the Loading Entries screen -const autoReloadJustAbortedReducer = reducerWithInitialState( - initialLogPositionState.autoReloadJustAborted -) - .case(stopAutoReload, () => true) - .case(startAutoReload, () => false) - .case(loadEntriesActionCreators.resolveDone, () => false) - .case(loadEntriesActionCreators.resolveFailed, () => false) - .case(loadEntriesActionCreators.resolve, () => false); - const autoReloadScrollLockReducer = reducerWithInitialState( initialLogPositionState.autoReloadScrollLock ) @@ -122,6 +116,5 @@ export const logPositionReducer = combineReducers({ updatePolicy: targetPositionUpdatePolicyReducer, visiblePositions: visiblePositionReducer, controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, - autoReloadJustAborted: autoReloadJustAbortedReducer, autoReloadScrollLock: autoReloadScrollLockReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts index 7a2fa86822c56..30fd4d3f77b5c 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts @@ -15,8 +15,6 @@ export const selectIsAutoReloading = (state: LogPositionState) => export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; -export const selectAutoReloadJustAborted = (state: LogPositionState) => state.autoReloadJustAborted; - export const selectFirstVisiblePosition = (state: LogPositionState) => state.visiblePositions.startKey ? state.visiblePositions.startKey : null; @@ -26,6 +24,13 @@ export const selectMiddleVisiblePosition = (state: LogPositionState) => export const selectLastVisiblePosition = (state: LogPositionState) => state.visiblePositions.endKey ? state.visiblePositions.endKey : null; +export const selectPagesBeforeAndAfter = (state: LogPositionState) => + state.visiblePositions + ? { + pagesBeforeStart: state.visiblePositions.pagesBeforeStart, + pagesAfterEnd: state.visiblePositions.pagesAfterEnd, + } + : { pagesBeforeStart: null, pagesAfterEnd: null }; export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) => state.controlsShouldDisplayTargetPosition; diff --git a/x-pack/legacy/plugins/infra/public/store/reducer.ts b/x-pack/legacy/plugins/infra/public/store/reducer.ts index 65b225d019603..2536ddbee401b 100644 --- a/x-pack/legacy/plugins/infra/public/store/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/reducer.ts @@ -7,19 +7,15 @@ import { combineReducers } from 'redux'; import { initialLocalState, localReducer, LocalState } from './local'; -import { initialRemoteState, remoteReducer, RemoteState } from './remote'; export interface State { local: LocalState; - remote: RemoteState; } export const initialState: State = { local: initialLocalState, - remote: initialRemoteState, }; export const reducer = combineReducers({ local: localReducer, - remote: remoteReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts b/x-pack/legacy/plugins/infra/public/store/remote/actions.ts deleted file mode 100644 index b38890afefb41..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { logEntriesActions } from './log_entries'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/epic.ts b/x-pack/legacy/plugins/infra/public/store/remote/epic.ts deleted file mode 100644 index 3b3ff602731cc..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/epic.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createLogEntriesEpic } from './log_entries'; - -export const createRemoteEpic = () => createLogEntriesEpic(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/index.ts b/x-pack/legacy/plugins/infra/public/store/remote/index.ts deleted file mode 100644 index c2843320bfd0c..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './actions'; -export * from './epic'; -export * from './reducer'; -export * from './selectors'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts deleted file mode 100644 index 02964bcb27e11..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/actions.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { loadEntriesActionCreators } from './operations/load'; -import { loadMoreEntriesActionCreators } from './operations/load_more'; - -const actionCreator = actionCreatorFactory('x-pack/infra/remote/log_entries'); - -export const setSourceId = actionCreator('SET_SOURCE_ID'); - -export const loadEntries = loadEntriesActionCreators.resolve; -export const loadMoreEntries = loadMoreEntriesActionCreators.resolve; - -export const loadNewerEntries = actionCreator('LOAD_NEWER_LOG_ENTRIES'); - -export const reloadEntries = actionCreator('RELOAD_LOG_ENTRIES'); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts deleted file mode 100644 index 0894a31996042..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/epic.ts +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Action } from 'redux'; -import { combineEpics, Epic, EpicWithState } from 'redux-observable'; -import { merge } from 'rxjs'; -import { exhaustMap, filter, map, withLatestFrom } from 'rxjs/operators'; - -import { logFilterActions, logPositionActions } from '../..'; -import { pickTimeKey, TimeKey, timeKeyIsBetween } from '../../../../common/time'; -import { - loadEntries, - loadMoreEntries, - loadNewerEntries, - reloadEntries, - setSourceId, -} from './actions'; -import { loadEntriesEpic } from './operations/load'; -import { loadMoreEntriesEpic } from './operations/load_more'; - -const LOAD_CHUNK_SIZE = 200; -const DESIRED_BUFFER_PAGES = 2; - -interface ManageEntriesDependencies { - selectLogEntriesStart: (state: State) => TimeKey | null; - selectLogEntriesEnd: (state: State) => TimeKey | null; - selectHasMoreLogEntriesBeforeStart: (state: State) => boolean; - selectHasMoreLogEntriesAfterEnd: (state: State) => boolean; - selectIsAutoReloadingLogEntries: (state: State) => boolean; - selectIsLoadingLogEntries: (state: State) => boolean; - selectLogFilterQueryAsJson: (state: State) => string | null; - selectVisibleLogMidpointOrTarget: (state: State) => TimeKey | null; -} - -export const createLogEntriesEpic = () => - combineEpics( - createEntriesEffectsEpic(), - loadEntriesEpic as EpicWithState, - loadMoreEntriesEpic as EpicWithState - ); - -export const createEntriesEffectsEpic = (): Epic< - Action, - Action, - State, - ManageEntriesDependencies -> => ( - action$, - state$, - { - selectLogEntriesStart, - selectLogEntriesEnd, - selectHasMoreLogEntriesBeforeStart, - selectHasMoreLogEntriesAfterEnd, - selectIsAutoReloadingLogEntries, - selectIsLoadingLogEntries, - selectLogFilterQueryAsJson, - selectVisibleLogMidpointOrTarget, - } -) => { - const filterQuery$ = state$.pipe(map(selectLogFilterQueryAsJson)); - const visibleMidpointOrTarget$ = state$.pipe( - map(selectVisibleLogMidpointOrTarget), - filter(isNotNull), - map(pickTimeKey) - ); - - const sourceId$ = action$.pipe( - filter(setSourceId.match), - map(({ payload }) => payload) - ); - - const shouldLoadAroundNewPosition$ = action$.pipe( - filter(logPositionActions.jumpToTargetPosition.match), - withLatestFrom(state$), - filter(([{ payload }, state]) => { - const entriesStart = selectLogEntriesStart(state); - const entriesEnd = selectLogEntriesEnd(state); - - return entriesStart && entriesEnd - ? !timeKeyIsBetween(entriesStart, entriesEnd, payload) - : true; - }), - map(([{ payload }]) => pickTimeKey(payload)) - ); - - const shouldLoadWithNewFilter$ = action$.pipe( - filter(logFilterActions.applyLogFilterQuery.match), - withLatestFrom(filterQuery$, (filterQuery, filterQueryString) => filterQueryString) - ); - - const shouldReload$ = merge(action$.pipe(filter(reloadEntries.match)), sourceId$); - - const shouldLoadMoreBefore$ = action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesBeforeStart } }) => pagesBeforeStart < DESIRED_BUFFER_PAGES), - withLatestFrom(state$), - filter( - ([action, state]) => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesBeforeStart(state) - ), - map(([action, state]) => selectLogEntriesStart(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - const shouldLoadMoreAfter$ = merge( - action$.pipe( - filter(logPositionActions.reportVisiblePositions.match), - filter(({ payload: { pagesAfterEnd } }) => pagesAfterEnd < DESIRED_BUFFER_PAGES), - withLatestFrom(state$, (action, state) => state), - filter( - state => - !selectIsAutoReloadingLogEntries(state) && - !selectIsLoadingLogEntries(state) && - selectHasMoreLogEntriesAfterEnd(state) - ) - ), - action$.pipe( - filter(loadNewerEntries.match), - withLatestFrom(state$, (action, state) => state) - ) - ).pipe( - map(state => selectLogEntriesEnd(state)), - filter(isNotNull), - map(pickTimeKey) - ); - - return merge( - shouldLoadAroundNewPosition$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadWithNewFilter$.pipe( - withLatestFrom(visibleMidpointOrTarget$, sourceId$), - exhaustMap(([filterQuery, timeKey, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldReload$.pipe( - withLatestFrom(visibleMidpointOrTarget$, filterQuery$, sourceId$), - exhaustMap(([_, timeKey, filterQuery, sourceId]) => [ - loadEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreAfter$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: 0, - countAfter: LOAD_CHUNK_SIZE, - filterQuery, - }), - ]) - ), - shouldLoadMoreBefore$.pipe( - withLatestFrom(filterQuery$, sourceId$), - exhaustMap(([timeKey, filterQuery, sourceId]) => [ - loadMoreEntries({ - sourceId, - timeKey, - countBefore: LOAD_CHUNK_SIZE, - countAfter: 0, - filterQuery, - }), - ]) - ) - ); -}; - -const isNotNull = (value: T | null): value is T => value !== null; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts deleted file mode 100644 index 8e00425526935..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as logEntriesActions from './actions'; -import * as logEntriesSelectors from './selectors'; - -export { logEntriesActions, logEntriesSelectors }; -export * from './epic'; -export * from './reducer'; -export { initialLogEntriesState, LogEntriesState } from './state'; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts deleted file mode 100644 index ce3193e57ab09..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load'; - -export const loadEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadEntriesActionCreators, - (state, action) => action.payload.result.data.source.logEntriesAround, - () => ({ - entries: [], - hasMoreAfter: false, - hasMoreBefore: false, - }) -); - -export const loadEntriesEpic = createGraphqlQueryEpic(logEntriesQuery, loadEntriesActionCreators); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts deleted file mode 100644 index 7651b039083cf..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/operations/load_more.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../../graphql/types'; -import { - getLogEntryIndexAfterTime, - getLogEntryIndexBeforeTime, - getLogEntryKey, -} from '../../../../utils/log_entry'; -import { - createGraphqlOperationActionCreators, - createGraphqlOperationReducer, - createGraphqlQueryEpic, -} from '../../../../utils/remote_state/remote_graphql_state'; -import { initialLogEntriesState } from '../state'; -import { logEntriesQuery } from './log_entries.gql_query'; - -const operationKey = 'load_more'; - -export const loadMoreEntriesActionCreators = createGraphqlOperationActionCreators< - LogEntriesQuery.Query, - LogEntriesQuery.Variables ->('log_entries', operationKey); - -export const loadMoreEntriesReducer = createGraphqlOperationReducer( - operationKey, - initialLogEntriesState, - loadMoreEntriesActionCreators, - (state, action) => { - const logEntriesAround = action.payload.result.data.source.logEntriesAround; - const newEntries = logEntriesAround.entries; - const oldEntries = state && state.entries ? state.entries : []; - const oldStart = state && state.start ? state.start : null; - const oldEnd = state && state.end ? state.end : null; - - if (newEntries.length <= 0) { - return state; - } - - if ((action.payload.params.countBefore || 0) > 0) { - const lastLogEntry = newEntries[newEntries.length - 1]; - const prependAtIndex = getLogEntryIndexAfterTime(oldEntries, getLogEntryKey(lastLogEntry)); - return { - start: logEntriesAround.start, - end: oldEnd, - hasMoreBefore: logEntriesAround.hasMoreBefore, - hasMoreAfter: state ? state.hasMoreAfter : logEntriesAround.hasMoreAfter, - entries: [...newEntries, ...oldEntries.slice(prependAtIndex)], - }; - } else if ((action.payload.params.countAfter || 0) > 0) { - const firstLogEntry = newEntries[0]; - const appendAtIndex = getLogEntryIndexBeforeTime(oldEntries, getLogEntryKey(firstLogEntry)); - return { - start: oldStart, - end: logEntriesAround.end, - hasMoreBefore: state ? state.hasMoreBefore : logEntriesAround.hasMoreBefore, - hasMoreAfter: logEntriesAround.hasMoreAfter, - entries: [...oldEntries.slice(0, appendAtIndex), ...newEntries], - }; - } else { - return state; - } - } -); - -export const loadMoreEntriesEpic = createGraphqlQueryEpic( - logEntriesQuery, - loadMoreEntriesActionCreators -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts deleted file mode 100644 index c0d60c4d336de..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/reducer.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import reduceReducers from 'reduce-reducers'; -import { Reducer } from 'redux'; - -import { loadEntriesReducer } from './operations/load'; -import { loadMoreEntriesReducer } from './operations/load_more'; -import { LogEntriesState } from './state'; - -export const logEntriesReducer = reduceReducers( - loadEntriesReducer, - loadMoreEntriesReducer -) as Reducer; diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts deleted file mode 100644 index 0306efc334a51..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/selectors.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createSelector } from 'reselect'; - -import { createGraphqlStateSelectors } from '../../../utils/remote_state/remote_graphql_state'; -import { LogEntriesRemoteState } from './state'; - -const entriesGraphlStateSelectors = createGraphqlStateSelectors(); - -export const selectEntries = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.entries : [] -); - -export const selectIsLoadingEntries = entriesGraphlStateSelectors.selectIsLoading; - -export const selectIsReloadingEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load' : false -); - -export const selectIsLoadingMoreEntries = createSelector( - entriesGraphlStateSelectors.selectIsLoading, - entriesGraphlStateSelectors.selectLoadingProgressOperationInfo, - (isLoading, operationInfo) => - isLoading && operationInfo ? operationInfo.operationKey === 'load_more' : false -); - -export const selectEntriesStart = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.start ? data.start : null -); - -export const selectEntriesEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data && data.end ? data.end : null -); - -export const selectHasMoreBeforeStart = createSelector( - entriesGraphlStateSelectors.selectData, - data => (data ? data.hasMoreBefore : true) -); - -export const selectHasMoreAfterEnd = createSelector(entriesGraphlStateSelectors.selectData, data => - data ? data.hasMoreAfter : true -); - -export const selectEntriesLastLoadedTime = entriesGraphlStateSelectors.selectLoadingResultTime; - -export const selectEntriesStartLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectEntriesEndLoadingState = entriesGraphlStateSelectors.selectLoadingState; - -export const selectFirstEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[0] : null -); - -export const selectLastEntry = createSelector(selectEntries, entries => - entries.length > 0 ? entries[entries.length - 1] : null -); - -export const selectLoadedEntriesTimeInterval = createSelector( - entriesGraphlStateSelectors.selectData, - data => ({ - end: data && data.end ? data.end.time : null, - start: data && data.start ? data.start.time : null, - }) -); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts b/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts deleted file mode 100644 index 8dbccf6c2bdd3..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/log_entries/state.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LogEntries as LogEntriesQuery } from '../../../graphql/types'; -import { - createGraphqlInitialState, - GraphqlState, -} from '../../../utils/remote_state/remote_graphql_state'; - -export type LogEntriesRemoteState = LogEntriesQuery.LogEntriesAround; -export type LogEntriesState = GraphqlState; - -export const initialLogEntriesState = createGraphqlInitialState(); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts b/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts deleted file mode 100644 index 2ab0a9a47ae86..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/reducer.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { combineReducers } from 'redux'; -import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries'; - -export interface RemoteState { - logEntries: LogEntriesState; -} - -export const initialRemoteState = { - logEntries: initialLogEntriesState, -}; - -export const remoteReducer = combineReducers({ - logEntries: logEntriesReducer, -}); diff --git a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts b/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts deleted file mode 100644 index b1daa2bc8110d..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/remote/selectors.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { globalizeSelectors } from '../../utils/typed_redux'; -import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries'; -import { RemoteState } from './reducer'; - -export const logEntriesSelectors = globalizeSelectors( - (state: RemoteState) => state.logEntries, - innerLogEntriesSelectors -); diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index 79a442789d6dd..aecba1779d036 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createSelector } from 'reselect'; - -import { getLogEntryAtTime } from '../utils/log_entry'; import { globalizeSelectors } from '../utils/typed_redux'; import { logFilterSelectors as localLogFilterSelectors, @@ -16,8 +13,6 @@ import { waffleTimeSelectors as localWaffleTimeSelectors, } from './local'; import { State } from './reducer'; -import { logEntriesSelectors as remoteLogEntriesSelectors } from './remote'; - /** * local selectors */ @@ -29,36 +24,3 @@ export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPosi export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); - -/** - * remote selectors - */ - -const selectRemote = (state: State) => state.remote; - -export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors); - -/** - * shared selectors - */ - -export const sharedSelectors = { - selectFirstVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectFirstVisiblePosition, - (entries, firstVisiblePosition) => - firstVisiblePosition ? getLogEntryAtTime(entries, firstVisiblePosition) : null - ), - selectMiddleVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectMiddleVisiblePosition, - (entries, middleVisiblePosition) => - middleVisiblePosition ? getLogEntryAtTime(entries, middleVisiblePosition) : null - ), - selectLastVisibleLogEntry: createSelector( - logEntriesSelectors.selectEntries, - logPositionSelectors.selectLastVisiblePosition, - (entries, lastVisiblePosition) => - lastVisiblePosition ? getLogEntryAtTime(entries, lastVisiblePosition) : null - ), -}; diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index d699db6af042e..601db0f56a693 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -12,7 +12,6 @@ import { map } from 'rxjs/operators'; import { createRootEpic, initialState, - logEntriesSelectors, logFilterSelectors, logPositionSelectors, reducer, @@ -38,11 +37,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) const middlewareDependencies = { postToApi$: observableApi.pipe(map(({ post }) => post)), apolloClient$: apolloClient, - selectIsLoadingLogEntries: logEntriesSelectors.selectIsLoadingEntries, - selectLogEntriesEnd: logEntriesSelectors.selectEntriesEnd, - selectLogEntriesStart: logEntriesSelectors.selectEntriesStart, - selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd, - selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, diff --git a/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx new file mode 100644 index 0000000000000..3bd3d31c745a9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/utils/redux_context.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { connect } from 'react-redux'; +import React, { createContext } from 'react'; +import { State, initialState } from '../store'; + +export const ReduxStateContext = createContext(initialState); + +const withRedux = connect((state: State) => state); +export const ReduxStateContextProvider = withRedux(({ children, ...state }) => { + return {children}; +}); diff --git a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts b/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts deleted file mode 100644 index 0cbc94516617b..0000000000000 --- a/x-pack/legacy/plugins/infra/public/utils/remote_state/remote_graphql_state.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ApolloError, ApolloQueryResult } from 'apollo-client'; -import { DocumentNode } from 'graphql'; -import { Action as ReduxAction } from 'redux'; -import { Epic } from 'redux-observable'; -import { from, Observable } from 'rxjs'; -import { catchError, filter, map, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; -import { Action, ActionCreator, actionCreatorFactory, Failure, Success } from 'typescript-fsa'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { createSelector } from 'reselect'; -import { InfraApolloClient } from '../../lib/lib'; -import { - isFailureLoadingResult, - isIdleLoadingProgress, - isRunningLoadingProgress, - isSuccessLoadingResult, - isUninitializedLoadingResult, - LoadingPolicy, - LoadingProgress, - LoadingResult, -} from '../loading_state'; - -export interface GraphqlState { - current: LoadingProgress>; - last: LoadingResult>; - data: State | undefined; -} - -interface OperationInfo { - operationKey: string; - variables: Variables; -} - -type ResolveDonePayload = Success>; -type ResolveFailedPayload = Failure; - -interface OperationActionCreators { - resolve: ActionCreator; - resolveStarted: ActionCreator; - resolveDone: ActionCreator>; - resolveFailed: ActionCreator>; -} - -export const createGraphqlInitialState = (initialData?: State): GraphqlState => ({ - current: { - progress: 'idle', - }, - last: { - result: 'uninitialized', - }, - data: initialData, -}); - -export const createGraphqlOperationActionCreators = ( - stateKey: string, - operationKey: string -): OperationActionCreators => { - const actionCreator = actionCreatorFactory(`x-pack/infra/remote/${stateKey}/${operationKey}`); - - const resolve = actionCreator('RESOLVE'); - const resolveEffect = actionCreator.async>('RESOLVE'); - - return { - resolve, - resolveStarted: resolveEffect.started, - resolveDone: resolveEffect.done, - resolveFailed: resolveEffect.failed, - }; -}; - -export const createGraphqlOperationReducer = ( - operationKey: string, - initialState: GraphqlState, - actionCreators: OperationActionCreators, - reduceSuccess: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state, - reduceFailure: ( - state: State | undefined, - action: Action> - ) => State | undefined = state => state -) => - reducerWithInitialState(initialState) - .caseWithAction(actionCreators.resolveStarted, (state, action) => ({ - ...state, - current: { - progress: 'running', - time: Date.now(), - parameters: { - operationKey, - variables: action.payload, - }, - }, - })) - .caseWithAction(actionCreators.resolveDone, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'success', - parameters: { - operationKey, - variables: action.payload.params, - }, - time: Date.now(), - isExhausted: false, - }, - data: reduceSuccess(state.data, action), - })) - .caseWithAction(actionCreators.resolveFailed, (state, action) => ({ - ...state, - current: { - progress: 'idle', - }, - last: { - result: 'failure', - reason: `${action.payload}`, - time: Date.now(), - parameters: { - operationKey, - variables: action.payload.params, - }, - }, - data: reduceFailure(state.data, action), - })) - .build(); - -export const createGraphqlQueryEpic = ( - graphqlQuery: DocumentNode, - actionCreators: OperationActionCreators -): Epic< - ReduxAction, - ReduxAction, - any, - { - apolloClient$: Observable; - } -> => (action$, state$, { apolloClient$ }) => - action$.pipe( - filter(actionCreators.resolve.match), - withLatestFrom(apolloClient$), - switchMap(([{ payload: variables }, apolloClient]) => - from( - apolloClient.query({ - query: graphqlQuery, - variables, - fetchPolicy: 'no-cache', - }) - ).pipe( - map(result => actionCreators.resolveDone({ params: variables, result })), - catchError(error => [actionCreators.resolveFailed({ params: variables, error })]), - startWith(actionCreators.resolveStarted(variables)) - ) - ) - ); - -export const createGraphqlStateSelectors = ( - selectState: (parentState: any) => GraphqlState = parentState => parentState -) => { - const selectData = createSelector(selectState, state => state.data); - - const selectLoadingProgress = createSelector(selectState, state => state.current); - const selectLoadingProgressOperationInfo = createSelector(selectLoadingProgress, progress => - isRunningLoadingProgress(progress) ? progress.parameters : null - ); - const selectIsLoading = createSelector(selectLoadingProgress, isRunningLoadingProgress); - const selectIsIdle = createSelector(selectLoadingProgress, isIdleLoadingProgress); - - const selectLoadingResult = createSelector(selectState, state => state.last); - const selectLoadingResultOperationInfo = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.parameters : null - ); - const selectLoadingResultTime = createSelector(selectLoadingResult, result => - !isUninitializedLoadingResult(result) ? result.time : null - ); - const selectIsUninitialized = createSelector(selectLoadingResult, isUninitializedLoadingResult); - const selectIsSuccess = createSelector(selectLoadingResult, isSuccessLoadingResult); - const selectIsFailure = createSelector(selectLoadingResult, isFailureLoadingResult); - - const selectLoadingState = createSelector( - selectLoadingProgress, - selectLoadingResult, - (loadingProgress, loadingResult) => ({ - current: loadingProgress, - last: loadingResult, - policy: { - policy: 'manual', - } as LoadingPolicy, - }) - ); - - return { - selectData, - selectIsFailure, - selectIsIdle, - selectIsLoading, - selectIsSuccess, - selectIsUninitialized, - selectLoadingProgress, - selectLoadingProgressOperationInfo, - selectLoadingResult, - selectLoadingResultOperationInfo, - selectLoadingResultTime, - selectLoadingState, - }; -}; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts index 01306901e9caa..834c991d5c6a4 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts @@ -5,7 +5,6 @@ */ import { startsWith, uniq, first } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { RequestHandlerContext } from 'src/core/server'; import { InfraDatabaseSearchResponse } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; @@ -105,8 +104,9 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { const bucketSelector = (response: InfraDatabaseSearchResponse<{}, DataSetResponse>) => (response.aggregations && response.aggregations.datasets.buckets) || []; - const handleAfterKey = createAfterKeyHandler('body.aggs.datasets.composite.after', input => - idx(input, _ => _.aggregations.datasets.after_key) + const handleAfterKey = createAfterKeyHandler( + 'body.aggs.datasets.composite.after', + input => input?.aggregations?.datasets?.after_key ); const buckets = await getAllCompositeData( diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts index 59a4e8911a94d..95769414832cc 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { RequestHandlerContext } from 'src/core/server'; import { InfraSnapshotGroupbyInput, @@ -70,8 +69,9 @@ const bucketSelector = ( response: InfraDatabaseSearchResponse<{}, InfraSnapshotAggregationResponse> ) => (response.aggregations && response.aggregations.nodes.buckets) || []; -const handleAfterKey = createAfterKeyHandler('body.aggregations.nodes.composite.after', input => - idx(input, _ => _.aggregations.nodes.after_key) +const handleAfterKey = createAfterKeyHandler( + 'body.aggregations.nodes.composite.after', + input => input?.aggregations?.nodes?.after_key ); const requestGroupedNodes = async ( diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js index 7b0e42283d5f5..100fa92a39990 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js @@ -22,7 +22,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { return provider({ config, indexPatterns, boolFilter }); }); - return function getSuggestions({ query, selectionStart, selectionEnd }) { + return function getSuggestions({ query, selectionStart, selectionEnd, signal }) { const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr(selectionEnd)}`; let cursorNode; @@ -34,7 +34,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { const { suggestionTypes = [] } = cursorNode; const suggestionsByType = suggestionTypes.map(type => { - return getSuggestionsByType[type](cursorNode); + return getSuggestionsByType[type](cursorNode, signal); }); return Promise.all(suggestionsByType) .then(suggestionsByType => dedup(flatten(suggestionsByType))); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js index 52bf347a94075..88e2fe812faa6 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js @@ -27,14 +27,14 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { suffix, fieldName, nestedPath, - }) { + }, signal) { const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; const fields = allFields.filter(field => field.name === fullFieldName); const query = `${prefix}${suffix}`.trim(); const { getSuggestions } = npStart.plugins.data; const suggestionsByField = fields.map(field => { - return getSuggestions(field.indexPatternTitle, field, query, boolFilter).then(data => { + return getSuggestions(field.indexPatternTitle, field, query, boolFilter, signal).then(data => { const quotedValues = data.map(value => typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}`); return wrapAsSuggestions(start, end, query, quotedValues); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js index 591833d646360..771984a1233e3 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js @@ -112,7 +112,7 @@ describe('Kuery value suggestions', function () { const spy = jest.spyOn(npStart.plugins.data, 'getSuggestions'); await getSuggestions({ fieldName, prefix, suffix }); expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined); + expect(spy).toBeCalledWith(expect.any(String), expect.any(Object), prefix + suffix, undefined, undefined); }); test('should escape quotes in suggestions', async () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss index a496cccc42a58..ed39beeb7d088 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/_datapanel.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/form_control_layout/mixins'; - .lnsInnerIndexPatternDataPanel { width: 100%; height: 100%; @@ -54,4 +52,3 @@ @include euiFormControlLayoutPadding(1, 'right'); @include euiFormControlLayoutPadding(1, 'left'); } - diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts index 03bbddb998b3c..72cbd1b861a05 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts @@ -129,7 +129,19 @@ const sampleIndexPatterns = { }; function indexPatternSavedObject({ id }: { id: keyof typeof sampleIndexPatterns }) { - const pattern = sampleIndexPatterns[id]; + const pattern = { + ...sampleIndexPatterns[id], + fields: [ + ...sampleIndexPatterns[id].fields, + { + name: 'description', + type: 'string', + aggregatable: false, + searchable: true, + esTypes: ['text'], + }, + ], + }; return { id, type: 'index-pattern', @@ -184,6 +196,16 @@ describe('loader', () => { expect(cache).toMatchObject(sampleIndexPatterns); }); + it('should not allow full text fields', async () => { + const cache = await loadIndexPatterns({ + cache: {}, + patterns: ['a', 'b'], + savedObjectsClient: mockClient(), + }); + + expect(cache).toMatchObject(sampleIndexPatterns); + }); + it('should apply field restrictions from typeMeta', async () => { const cache = await loadIndexPatterns({ cache: {}, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 40d9d25869c7a..89d4224a7df14 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -284,10 +284,7 @@ function fromSavedObject( type, title: attributes.title, fields: (JSON.parse(attributes.fields) as IndexPatternField[]) - .filter( - ({ type: fieldType, esTypes }) => - fieldType !== 'string' || (esTypes && esTypes.includes('keyword')) - ) + .filter(({ aggregatable }) => !!aggregatable) .concat(documentField), typeMeta: attributes.typeMeta ? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo) diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index dc0756978010e..876993eed7795 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -21,12 +21,14 @@ export class TOCEntry extends React.Component { state = { displayName: null, + hasLegendDetails: false, shouldShowModal: false }; componentDidMount() { this._isMounted = true; this._updateDisplayName(); + this._loadHasLegendDetails(); } componentWillUnmount() { @@ -35,6 +37,7 @@ export class TOCEntry extends React.Component { componentDidUpdate() { this._updateDisplayName(); + this._loadHasLegendDetails(); } _toggleLayerDetailsVisibility = () => { @@ -45,6 +48,13 @@ export class TOCEntry extends React.Component { } } + async _loadHasLegendDetails() { + const hasLegendDetails = await this.props.layer.hasLegendDetails(); + if (this._isMounted && hasLegendDetails !== this.state.hasLegendDetails) { + this.setState({ hasLegendDetails }); + } + } + async _updateDisplayName() { const label = await this.props.layer.getDisplayName(); if (this._isMounted) { @@ -143,7 +153,7 @@ export class TOCEntry extends React.Component { } _renderDetailsToggle() { - if (!this.props.layer.hasLegendDetails()) { + if (!this.state.hasLegendDetails) { return null; } @@ -223,7 +233,7 @@ export class TOCEntry extends React.Component { } _renderLegendDetails = () => { - if (!this.props.isLegendDetailsOpen || !this.props.layer.hasLegendDetails()) { + if (!this.props.isLegendDetailsOpen || !this.state.hasLegendDetails) { return null; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js index ebb9fc27be149..0c4999ce4e41f 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js @@ -13,6 +13,7 @@ const LAYER_ID = '1'; const mockLayer = { getId: () => { return LAYER_ID; }, + hasLegendDetails: async () => { return true; }, renderLegendDetails: () => { return (
TOC details mock
); }, getDisplayName: () => { return 'layer 1'; }, isVisible: () => { return true; }, diff --git a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js index 0342975ce3192..632ed22474022 100644 --- a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js @@ -98,7 +98,7 @@ export class HeatmapLayer extends VectorLayer { return 'heatmap'; } - hasLegendDetails() { + async hasLegendDetails() { return true; } diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index b1f3c32f267b9..c646d0ac5c1df 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -186,7 +186,7 @@ export class AbstractLayer { }; } - hasLegendDetails() { + async hasLegendDetails() { return false; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index f4cb43ad90146..de1b47ea28a91 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -25,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; +import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; const MAX_GEOTILE_LEVEL = 29; @@ -216,14 +217,14 @@ export class ESGeoGridSource extends AbstractESAggSource { ]; } - _createDefaultLayerDescriptor(options) { - if (this._descriptor.requestType === RENDER_AS.HEATMAP) { - return HeatmapLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options - }); - } + _createHeatmapLayerDescriptor(options) { + return HeatmapLayer.createDescriptor({ + sourceDescriptor: this._descriptor, + ...options + }); + } + _createVectorLayerDescriptor(options) { const descriptor = VectorLayer.createDescriptor({ sourceDescriptor: this._descriptor, ...options @@ -244,6 +245,18 @@ export class ESGeoGridSource extends AbstractESAggSource { color: 'Blues' } }, + [VECTOR_STYLES.LINE_COLOR]: { + type: StaticStyleProperty.type, + options: { + color: '#FFF' + } + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: StaticStyleProperty.type, + options: { + size: 0 + } + }, [VECTOR_STYLES.ICON_SIZE]: { type: DynamicStyleProperty.type, options: { @@ -253,8 +266,6 @@ export class ESGeoGridSource extends AbstractESAggSource { name: COUNT_PROP_NAME, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); @@ -264,15 +275,15 @@ export class ESGeoGridSource extends AbstractESAggSource { createDefaultLayer(options) { if (this._descriptor.requestType === RENDER_AS.HEATMAP) { return new HeatmapLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), + layerDescriptor: this._createHeatmapLayerDescriptor(options), source: this }); } - const layerDescriptor = this._createDefaultLayerDescriptor(options); + const layerDescriptor = this._createVectorLayerDescriptor(options); const style = new VectorStyle(layerDescriptor.style, this); return new VectorLayer({ - layerDescriptor: layerDescriptor, + layerDescriptor, source: this, style }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 4eb0a952defba..c897ee2a5b2d0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -146,8 +146,6 @@ export class ESPewPewSource extends AbstractESAggSource { name: COUNT_PROP_NAME, origin: SOURCE_DATA_ID_ORIGIN }, - minSize: 4, - maxSize: 32, } } }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js index dc5098c4d6d4d..6bd8b64ddf832 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/style_property_legend_row.js @@ -16,77 +16,16 @@ const EMPTY_VALUE = ''; export class StylePropertyLegendRow extends Component { - state = { - label: '', - hasLoadedFieldFormatter: false, - } - - componentDidMount() { - this._isMounted = true; - this._prevLabel = undefined; - this._fieldValueFormatter = undefined; - this._loadLabel(); - this._loadFieldFormatter(); - } - - componentDidUpdate() { - // label could change so it needs to be loaded on update - this._loadLabel(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadFieldFormatter() { - if (this.props.style.isDynamic() && this.props.style.isComplete() && this.props.style.getField().getSource()) { - const field = this.props.style.getField(); - const source = field.getSource(); - this._fieldValueFormatter = await source.getFieldFormatter(field.getName()); - } else { - this._fieldValueFormatter = null; - } - if (this._isMounted) { - this.setState({ hasLoadedFieldFormatter: true }); - } - } - - _loadLabel = async () => { - if (this._excludeFromHeader()) { - return; - } - - // have to load label and then check for changes since field name stays constant while label may change - const label = await this.props.style.getField().getLabel(); - if (this._prevLabel === label) { - return; - } - - this._prevLabel = label; - if (this._isMounted) { - this.setState({ label }); - } - } - - _excludeFromHeader() { - return !this.props.style.isDynamic() || !this.props.style.isComplete() || !this.props.style.getField().getName(); - } - _formatValue = value => { - if (!this.state.hasLoadedFieldFormatter || !this._fieldValueFormatter || value === EMPTY_VALUE) { + if (!this.props.fieldFormatter || value === EMPTY_VALUE) { return value; } - return this._fieldValueFormatter(value); + return this.props.fieldFormatter(value); } render() { const { range, style } = this.props; - if (this._excludeFromHeader()) { - return null; - } - - const header = style.renderHeader(); const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); const minLabel = this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange ? `< ${min}` : min; @@ -96,17 +35,19 @@ export class StylePropertyLegendRow extends Component { return ( ); } } StylePropertyLegendRow.propTypes = { + label: PropTypes.string, + fieldFormatter: PropTypes.func, range: rangeShape, style: PropTypes.object }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index e339cad6af973..5f4139432a912 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -4,29 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import _ from 'lodash'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { rangeShape } from '../style_option_shapes'; import { StylePropertyLegendRow } from './style_property_legend_row'; -export function VectorStyleLegend({ styleProperties }) { - return styleProperties.map(styleProperty => { - return ( - - ); - }); -} +export class VectorStyleLegend extends Component { + + state = { + rows: [], + } + + componentDidMount() { + this._isMounted = true; + this._prevRowDescriptors = undefined; + this._loadRows(); + } + + componentDidUpdate() { + this._loadRows(); + } -const stylePropertyShape = PropTypes.shape({ - range: rangeShape, - style: PropTypes.object -}); + componentWillUnmount() { + this._isMounted = false; + } + + _loadRows = _.debounce(async () => { + const rows = await this.props.loadRows(); + const rowDescriptors = rows.map(row => { + return { + label: row.label, + range: row.range, + styleOptions: row.style.getOptions(), + }; + }); + if (this._isMounted && !_.isEqual(rowDescriptors, this._prevRowDescriptors)) { + this._prevRowDescriptors = rowDescriptors; + this.setState({ rows }); + } + }, 100); + + render() { + return this.state.rows.map(rowProps => { + return ( + + ); + }); + } +} VectorStyleLegend.propTypes = { - styleProperties: PropTypes.arrayOf(stylePropertyShape).isRequired + loadRows: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 4279fbbe0fbb3..1d5815a84920c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -7,21 +7,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ValidatedDualRange } from 'ui/validated_range'; -import { DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE } from '../../vector_style_defaults'; +import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { const onSizeChange = ([min, max]) => { onChange({ - minSize: Math.max(DEFAULT_MIN_SIZE, parseInt(min, 10)), - maxSize: Math.min(DEFAULT_MAX_SIZE, parseInt(max, 10)), + minSize: Math.max(MIN_SIZE, parseInt(min, 10)), + maxSize: Math.min(MAX_SIZE, parseInt(max, 10)), }); }; return ( { + const hasFeature = hasFeatureType[featureTypeKey]; + return featureTypeKey === featureType + ? isOnlyTargetFeatureType && hasFeature + : isOnlyTargetFeatureType && !hasFeature; + }, true); +} + export function scaleValue(value, range) { if (isNaN(value) || !range) { return -1; //Nothing to scale, put outside scaled range diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js index a25e3bf8684c9..1d6e6b256463e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -4,7 +4,57 @@ * you may not use this file except in compliance with the Elastic License. */ -import { scaleValue } from './style_util'; +import { isOnlySingleFeatureType, scaleValue } from './style_util'; +import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; + +describe('isOnlySingleFeatureType', () => { + describe('source supports single feature type', () => { + const supportedFeatures = [VECTOR_SHAPE_TYPES.POINT]; + + test('Is only single feature type when only supported feature type is target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures)).toBe(true); + }); + + test('Is not single feature type when only supported feature type is not target feature type', () => { + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures)).toBe(false); + }); + }); + + describe('source supports multiple feature types', () => { + const supportedFeatures = [ + VECTOR_SHAPE_TYPES.POINT, + VECTOR_SHAPE_TYPES.LINE, + VECTOR_SHAPE_TYPES.POLYGON + ]; + + test('Is only single feature type when data only has target feature type', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: false, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(true); + }); + + test('Is not single feature type when data has multiple feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: true, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: true, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures, hasFeatureType)).toBe(false); + }); + + test('Is not single feature type when data does not have target feature types', () => { + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: false, + [VECTOR_SHAPE_TYPES.LINE]: true, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)).toBe(false); + }); + }); +}); describe('scaleValue', () => { test('Should scale value between 0 and 1', () => { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js index 21d55b9a75991..027d558d3c033 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_constants.js @@ -7,4 +7,4 @@ export const SYMBOLIZE_AS_CIRCLE = 'circle'; export const SYMBOLIZE_AS_ICON = 'icon'; -export const DEFAULT_ICON_SIZE = 10; +export const DEFAULT_ICON_SIZE = 6; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 53794f2043aad..426af96b63ba2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -7,7 +7,7 @@ import _ from 'lodash'; import React from 'react'; import { VectorStyleEditor } from './components/vector_style_editor'; -import { getDefaultProperties, VECTOR_STYLES } from './vector_style_defaults'; +import { getDefaultProperties, LINE_STYLES, POLYGON_STYLES, VECTOR_STYLES } from './vector_style_defaults'; import { AbstractStyle } from '../abstract_style'; import { GEO_JSON_TYPE, @@ -21,7 +21,7 @@ import { VectorStyleLegend } from './components/legend/vector_style_legend'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants'; import { getMakiSymbolAnchor } from './symbol_utils'; -import { getComputedFieldName, scaleValue } from './style_util'; +import { getComputedFieldName, isOnlySingleFeatureType, scaleValue } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; import { DynamicSizeProperty } from './properties/dynamic_size_property'; @@ -271,32 +271,23 @@ export class VectorStyle extends AbstractStyle { return styleProperties.filter(styleProperty => (styleProperty.isDynamic() && styleProperty.isComplete())); } - _checkIfOnlyFeatureType = async (featureType) => { - const supportedFeatures = await this._source.getSupportedShapeTypes(); - - if (supportedFeatures.length === 1) { - return supportedFeatures[0] === featureType; - } - - if (!this._descriptor.__styleMeta || !this._descriptor.__styleMeta.hasFeatureType) { - return false; - } - - const featureTypes = Object.keys(this._descriptor.__styleMeta.hasFeatureType); - return featureTypes.reduce((isOnlySingleFeatureType, featureTypeKey) => { - const hasFeature = this._descriptor.__styleMeta.hasFeatureType[featureTypeKey]; - return featureTypeKey === featureType - ? isOnlySingleFeatureType && hasFeature - : isOnlySingleFeatureType && !hasFeature; - }, true); + _isOnlySingleFeatureType = async (featureType) => { + return isOnlySingleFeatureType( + featureType, + await this._source.getSupportedShapeTypes(), + this._getStyleMeta().hasFeatureType); } _getIsPointsOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POINT); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT); } _getIsLinesOnly = async () => { - return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.LINE); + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE); + } + + _getIsPolygonsOnly = async () => { + return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POLYGON); } _getFieldRange = (fieldName) => { @@ -352,6 +343,10 @@ export class VectorStyle extends AbstractStyle { }; } + _getStyleMeta = () => { + return _.get(this._descriptor, '__styleMeta', {}); + } + getIcon = () => { const styles = this.getRawProperties(); const symbolId = this.arePointsSymbolizedAsCircles() @@ -368,21 +363,43 @@ export class VectorStyle extends AbstractStyle { ); } - renderLegendDetails() { - const styles = this._getAllStyleProperties(); - const styleProperties = styles.map((style) => { - return { - // eslint-disable-next-line max-len - range: (style.isDynamic() && style.isComplete() && style.getField().getName()) ? this._getFieldRange(style.getField().getName()) : null, - style: style - }; + async _getLegendDetailStyleProperties() { + const isLinesOnly = await this._getIsLinesOnly(); + const isPolygonsOnly = await this._getIsPolygonsOnly(); + + return this.getDynamicPropertiesArray().filter(styleProperty => { + if (isLinesOnly) { + return LINE_STYLES.includes(styleProperty.getStyleName()); + } + + if (isPolygonsOnly) { + return POLYGON_STYLES.includes(styleProperty.getStyleName()); + } + + return true; }); + } - return ( - - ); + async hasLegendDetails() { + const styles = await this._getLegendDetailStyleProperties(); + return styles.length > 0; + } + + renderLegendDetails() { + const loadRows = async () => { + const styles = await this._getLegendDetailStyleProperties(); + const promises = styles.map(async (style) => { + return { + label: await style.getField().getLabel(), + fieldFormatter: await this._source.getFieldFormatter(style.getField().getName()), + range: this._getFieldRange(style.getField().getName()), + style, + }; + }); + return await Promise.all(promises); + }; + + return ; } _getStyleFields() { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index b834fb842389e..33218e909ff92 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -14,8 +14,8 @@ import { const DEFAULT_ICON = 'airfield'; -export const DEFAULT_MIN_SIZE = 1; -export const DEFAULT_MAX_SIZE = 64; +export const MIN_SIZE = 1; +export const MAX_SIZE = 64; export const DEFAULT_SIGMA = 3; export const VECTOR_STYLES = { @@ -27,6 +27,9 @@ export const VECTOR_STYLES = { ICON_ORIENTATION: 'iconOrientation' }; +export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; +export const POLYGON_STYLES = [VECTOR_STYLES.FILL_COLOR, VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; + export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), @@ -109,8 +112,8 @@ export function getDefaultDynamicProperties() { [VECTOR_STYLES.LINE_WIDTH]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { - minSize: DEFAULT_MIN_SIZE, - maxSize: DEFAULT_MAX_SIZE, + minSize: 1, + maxSize: 10, field: undefined, fieldMetaOptions: { isEnabled: true, @@ -121,8 +124,8 @@ export function getDefaultDynamicProperties() { [VECTOR_STYLES.ICON_SIZE]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { - minSize: DEFAULT_MIN_SIZE, - maxSize: DEFAULT_MAX_SIZE, + minSize: 4, + maxSize: 32, field: undefined, fieldMetaOptions: { isEnabled: true, diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 7e831115e6dba..60a6201ac872c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -145,8 +145,8 @@ export class VectorLayer extends AbstractLayer { return 'vector'; } - hasLegendDetails() { - return this._style.getDynamicPropertiesArray().length > 0; + async hasLegendDetails() { + return this._style.hasLegendDetails(); } renderLegendDetails() { diff --git a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss index ea5c245d9e39f..25bf3597c3466 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss +++ b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/_chart_tooltip.scss @@ -1,6 +1,3 @@ -@import '@elastic/eui/src/components/tool_tip/variables'; -@import '@elastic/eui/src/components/tool_tip/mixins'; - .mlChartTooltip { @include euiToolTipStyle('s'); @include euiFontSizeXS; @@ -50,5 +47,4 @@ &--hidden { opacity: 0; } - } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index b9a03fdbc548b..0642c1fbe6186 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -8,7 +8,6 @@ import { useEffect } from 'react'; import { BehaviorSubject } from 'rxjs'; import { filter, distinctUntilChanged } from 'rxjs/operators'; import { Subscription } from 'rxjs'; -import { idx } from '@kbn/elastic-idx'; import { cloneDeep } from 'lodash'; import { ml } from '../../services/ml_api_service'; import { Dictionary } from '../../../../common/types/common'; @@ -242,12 +241,13 @@ export const useRefreshAnalyticsList = ( const DEFAULT_SIG_FIGS = 3; export function getValuesFromResponse(response: RegressionEvaluateResponse) { - let meanSquaredError = idx(response, _ => _.regression.mean_squared_error.error) as number; + let meanSquaredError = response?.regression?.mean_squared_error?.error; + if (meanSquaredError) { meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS)); } - let rSquared = idx(response, _ => _.regression.r_squared.value) as number; + let rSquared = response?.regression?.r_squared?.value; if (rSquared) { rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS)); } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index a650a867aea60..91b73307ef56c 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -6,7 +6,6 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import moment from 'moment-timezone'; -import { idx } from '@kbn/elastic-idx'; import { EuiIcon, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui'; @@ -64,7 +63,7 @@ export const ExpandedRow: FC = ({ item }) => { const [generalizationEval, setGeneralizationEval] = useState(defaultEval); const [isLoadingTraining, setIsLoadingTraining] = useState(false); const [isLoadingGeneralization, setIsLoadingGeneralization] = useState(false); - const index = idx(item, _ => _.config.dest.index) as string; + const index = item?.config?.dest?.index; const dependentVariable = getDependentVar(item.config.analysis); const predictionFieldName = getPredictionFieldName(item.config.analysis); // default is 'ml' diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx index b85591d13998a..49381e3b1c031 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx @@ -56,6 +56,6 @@ describe('Data Frame Analytics: ', () => { expect(options.at(2).props().value).toBe('regression'); const row2 = euiFormRows.at(1); - expect(row2.find('p').text()).toBe('Enable advanced editor'); + expect(row2.find('EuiSwitch').text()).toBe('Enable advanced editor'); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 918c42f480e1e..eda80ca64c86f 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { validateIndexPattern } from 'ui/index_patterns'; @@ -43,7 +42,7 @@ export const mmlUnitInvalidErrorMessage = i18n.translate( const getSourceIndexString = (state: State) => { const { jobConfig } = state; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (typeof sourceIndex === 'string') { return sourceIndex; @@ -70,17 +69,17 @@ export const validateAdvancedEditor = (state: State): State => { // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. // If there are no messages, it means the index pattern is valid. let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; - const sourceIndex = idx(jobConfig, _ => _.source.index); + const sourceIndex = jobConfig?.source?.index; if (sourceIndexNameValid) { if (typeof sourceIndex === 'string') { sourceIndexNameValid = !sourceIndex.includes(','); } if (Array.isArray(sourceIndex)) { - sourceIndexNameValid = !sourceIndex.some(d => d.includes(',')); + sourceIndexNameValid = !sourceIndex.some(d => d?.includes(',')); } } - const destinationIndexName = idx(jobConfig, _ => _.dest.index) || ''; + const destinationIndexName = jobConfig?.dest?.index ?? ''; const destinationIndexNameEmpty = destinationIndexName === ''; const destinationIndexNameValid = isValidIndexName(destinationIndexName); const destinationIndexPatternTitleExists = diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts index ec70c54892a0e..547a55da7438b 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.test.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { getInitialState, getJobConfigFromFormState } from './state'; describe('useCreateAnalyticsForm', () => { @@ -17,15 +15,15 @@ describe('useCreateAnalyticsForm', () => { const jobConfig = getJobConfigFromFormState(state.form); - expect(idx(jobConfig, _ => _.dest.index)).toBe('the-destination-index'); - expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index'); - expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]); - expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined'); + expect(jobConfig?.dest?.index).toBe('the-destination-index'); + expect(jobConfig?.source?.index).toBe('the-source-index'); + expect(jobConfig?.analyzed_fields?.excludes).toStrictEqual([]); + expect(typeof jobConfig?.analyzed_fields?.includes).toBe('undefined'); // test the conversion of comma-separated Kibana index patterns to ES array based index patterns state.form.sourceIndex = 'the-source-index-1,the-source-index-2'; const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form); - expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([ + expect(jobConfigSourceIndexArray?.source?.index).toStrictEqual([ 'the-source-index-1', 'the-source-index-2', ]); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index cac14e654016f..b2f9442f48edb 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -7,7 +7,6 @@ import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; -import { idx } from '@kbn/elastic-idx'; import { SimpleSavedObject } from 'src/core/public'; import { ml } from '../../../../../services/ml_api_service'; @@ -229,9 +228,9 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const indexPatternsMap: SourceIndexMap = {}; const savedObjects = (await kibanaContext.indexPatterns.getCache()) || []; savedObjects.forEach((obj: SimpleSavedObject>) => { - const title = idx(obj, _ => _.attributes.title); + const title = obj?.attributes?.title; if (title !== undefined) { - const id = idx(obj, _ => _.id) || ''; + const id = obj?.id || ''; indexPatternsMap[title] = { label: title, value: id }; } }); diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss index 6137a21d0bd8d..39a87ece68ac9 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/file_based/components/fields_stats/_field_stats_card.scss @@ -41,12 +41,13 @@ background-color: #920000; } - .type-other, .unknown { + .type-other, + .unknown { background-color: #bfa180; } // Use euiPanel styling - @include euiPanel($selector: 'card-contents'); + @include euiPanel($selector: '.card-contents'); .card-contents { height: 378px; @@ -68,12 +69,16 @@ padding-bottom: 0px; } - .stat.min, .stat.max, .stat.median { + .stat.min, + .stat.max, + .stat.median { width: 30%; display: inline-block; } - .stat.min.value, .stat.max.value, .stat.median.value { + .stat.min.value, + .stat.max.value, + .stat.median.value { font-size: $euiFontSizeS; @include euiTextTruncate; } diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss index ca7d8e3f31c58..b4fd521f21bec 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/_field_data_card.scss @@ -2,7 +2,6 @@ height: 420px; width: 360px; - // Note the names of these styles need to match the type of the field they are displaying. .boolean { background-color: $euiColorVis5; @@ -36,13 +35,13 @@ background-color: $euiColorVis9; } - .type-other, .unknown { + .type-other, + .unknown { background-color: $euiColorVis6; } - // Use euiPanel styling - @include euiPanel($selector: 'mlFieldDataCard__content'); + @include euiPanel($selector: '.mlFieldDataCard__content'); .mlFieldDataCard__content { @include euiFontSizeS; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index aaa5142f519ac..e7e5e8aa64f7b 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Job, Datafeed, Detector } from '../configs'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { @@ -120,10 +119,9 @@ function getDetectors(job: Job, datafeed: Datafeed) { ) { // distinct count detector, field has been removed. // determine field from datafeed aggregations - const field = idx( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const field = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + if (field !== undefined) { detectors = [ { @@ -193,10 +191,9 @@ function processFieldlessAggs(detectors: Detector[]) { export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean { const detectors = job.analysis_config.detectors; - const distinctCountField = idx( - datafeed, - _ => _.aggregations.buckets.aggregations.dc_region.cardinality.field - ); + const distinctCountField = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality + ?.field as string; + // if distinctCountField is undefined, and any detectors contain a sparse data function // return true if (distinctCountField === undefined) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts index cfebca70d27b5..0265129d9ccab 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { mlJobService } from '../../../../services/job_service'; import { loadIndexPatterns, getIndexPatternIdFromName } from '../../../../util/index_utils'; import { CombinedJob } from '../../common/job_creator/configs'; @@ -29,7 +28,7 @@ export async function preConfiguredJobRedirect() { } function getWizardUrlFromCloningJob(job: CombinedJob) { - const created = idx(job, _ => _.custom_settings.created_by); + const created = job?.custom_settings?.created_by; let page = ''; if (created === CREATED_BY_LABEL.SINGLE_METRIC) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts index 1facc761d6379..589803b33e11c 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index c3216773c1a32..ac3f3fef97c70 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -1,10 +1,6 @@ // Should import both the EUI constants and any Kibana ones that are considered global @import 'src/legacy/ui/public/styles/styling_constants'; -// ML needs EUI card styling till it fully adopts React components -@import '@elastic/eui/src/components/panel/variables'; -@import '@elastic/eui/src/components/panel/mixins'; - // ML has it's own variables for coloring @import 'application/variables'; @@ -16,7 +12,6 @@ // Protect the rest of Kibana from ML generic namespacing // SASSTODO: Prefix ml selectors instead #ml-app { - // App level @import 'application/app'; diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index bf309c65556a8..6ebc43bb92737 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/legacy/plugins/remote_clusters/public/app/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -40,6 +40,7 @@ Array [
-

Skip if unavailable -

+
@@ -367,6 +370,7 @@ Array [
-

Skip if unavailable -

+
, diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js index b14531d8f6efd..9a3aed548dcc9 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { addIndexPatternType } from 'ui/management/index_pattern_creation'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternCreationConfig } from './rollup_index_pattern_creation_config'; export function initIndexPatternCreation() { - addIndexPatternType(RollupIndexPatternCreationConfig); + managementSetup.indexPattern.creation.add(RollupIndexPatternCreationConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index 104d632e3ac3d..8448f9da19f45 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -5,32 +5,44 @@ */ import React from 'react'; -import { IndexPatternCreationConfig } from 'ui/management/index_pattern_creation'; +import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; import { RollupPrompt } from './components/rollup_prompt'; import { setHttpClient, getRollupIndices } from '../services/api'; import { i18n } from '@kbn/i18n'; -const rollupIndexPatternTypeName = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', - { defaultMessage: 'rollup index pattern' }); +const rollupIndexPatternTypeName = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', + { defaultMessage: 'rollup index pattern' } +); -const rollupIndexPatternButtonText = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', - { defaultMessage: 'Rollup index pattern' }); +const rollupIndexPatternButtonText = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonText', + { defaultMessage: 'Rollup index pattern' } +); -const rollupIndexPatternButtonDescription = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', - { defaultMessage: 'Perform limited aggregations against summarized data' }); +const rollupIndexPatternButtonDescription = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultButtonDescription', + { defaultMessage: 'Perform limited aggregations against summarized data' } +); -const rollupIndexPatternNoMatchError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', - { defaultMessage: 'Rollup index pattern error: must match one rollup index' }); +const rollupIndexPatternNoMatchError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.noMatchError', + { defaultMessage: 'Rollup index pattern error: must match one rollup index' } +); -const rollupIndexPatternTooManyMatchesError = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', - { defaultMessage: 'Rollup index pattern error: can only match one rollup index' }); +const rollupIndexPatternTooManyMatchesError = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.tooManyMatchesError', + { defaultMessage: 'Rollup index pattern error: can only match one rollup index' } +); -const rollupIndexPatternIndexLabel = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', - { defaultMessage: 'Rollup' }); +const rollupIndexPatternIndexLabel = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.indexLabel', + { defaultMessage: 'Rollup' } +); export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig { - static key = 'rollup'; + key = 'rollup'; constructor(options) { super({ @@ -60,76 +72,85 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig async getIndexPatternCreationOption(urlHandler) { await this.settingUp; - return this.rollupIndices && this.rollupIndices.length ? { - text: rollupIndexPatternButtonText, - description: rollupIndexPatternButtonDescription, - testSubj: `createRollupIndexPatternButton`, - isBeta: this.isBeta, - onClick: () => { - urlHandler('/management/kibana/index_pattern?type=rollup'); - }, - } : null; + return this.rollupIndices && this.rollupIndices.length + ? { + text: rollupIndexPatternButtonText, + description: rollupIndexPatternButtonDescription, + testSubj: `createRollupIndexPatternButton`, + isBeta: this.isBeta, + onClick: () => { + urlHandler('/management/kibana/index_pattern?type=rollup'); + }, + } + : null; } - isRollupIndex = (indexName) => { + isRollupIndex = indexName => { return this.rollupIndices.includes(indexName); - } + }; getIndexTags(indexName) { - return this.isRollupIndex(indexName) ? [{ - key: this.type, - name: rollupIndexPatternIndexLabel, - }] : []; + return this.isRollupIndex(indexName) + ? [ + { + key: this.type, + name: rollupIndexPatternIndexLabel, + }, + ] + : []; } - checkIndicesForErrors = (indices) => { + checkIndicesForErrors = indices => { this.rollupIndex = null; - if(!indices || !indices.length) { + if (!indices || !indices.length) { return; } const rollupIndices = indices.filter(index => this.isRollupIndex(index.name)); - if(!rollupIndices.length) { + if (!rollupIndices.length) { return [rollupIndexPatternNoMatchError]; - } else if(rollupIndices.length > 1) { + } else if (rollupIndices.length > 1) { return [rollupIndexPatternTooManyMatchesError]; } const rollupIndexName = rollupIndices[0].name; const error = this.rollupIndicesCapabilities[rollupIndexName].error; - if(error) { - const errorMessage = i18n.translate('xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', { - defaultMessage: 'Rollup index pattern error: {error}', - values: { - error + if (error) { + const errorMessage = i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.createIndex.uncaughtError', + { + defaultMessage: 'Rollup index pattern error: {error}', + values: { + error, + }, } - }); + ); return [errorMessage]; } this.rollupIndex = rollupIndexName; - } + }; getIndexPatternMappings = () => { - return this.rollupIndex ? { - type: this.type, - typeMeta: { - params: { - rollup_index: this.rollupIndex, + return this.rollupIndex + ? { + type: this.type, + typeMeta: { + params: { + rollup_index: this.rollupIndex, + }, + aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, }, - aggs: this.rollupIndicesCapabilities[this.rollupIndex].aggs, - }, - } : {}; - } + } + : {}; + }; renderPrompt = () => { - return ( - - ); - } + return ; + }; getFetchForWildcardOptions = () => { return { @@ -138,5 +159,5 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig rollup_index: this.rollupIndex, }, }; - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js index d5ee0d8c6976e..173c28826436b 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/register.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfigRegistry } from 'ui/management/index_pattern_list'; +import { setup as managementSetup } from '../../../../../../src/legacy/core_plugins/management/public/legacy'; import { RollupIndexPatternListConfig } from './rollup_index_pattern_list_config'; export function initIndexPatternList() { - IndexPatternListConfigRegistry.register(() => RollupIndexPatternListConfig); + managementSetup.indexPattern.list.add(RollupIndexPatternListConfig); } diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index 92c3ac20a2819..1d8ae6d425065 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -3,49 +3,59 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternListConfig } from 'ui/management/index_pattern_list'; +import { IndexPatternListConfig } from '../../../../../../src/legacy/core_plugins/management/public'; function isRollup(indexPattern) { - return indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup'); + return ( + indexPattern.type === 'rollup' || (indexPattern.get && indexPattern.get('type') === 'rollup') + ); } export class RollupIndexPatternListConfig extends IndexPatternListConfig { - static key = 'rollup'; - - getIndexPatternTags = (indexPattern) => { - return isRollup(indexPattern) ? [{ - key: 'rollup', - name: 'Rollup', - }] : []; - } + key = 'rollup'; + + getIndexPatternTags = indexPattern => { + return isRollup(indexPattern) + ? [ + { + key: 'rollup', + name: 'Rollup', + }, + ] + : []; + }; getFieldInfo = (indexPattern, field) => { - if(!isRollup(indexPattern)) { + if (!isRollup(indexPattern)) { return []; } const allAggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs; const fieldAggs = allAggs && Object.keys(allAggs).filter(agg => allAggs[agg][field]); - if(!fieldAggs || !fieldAggs.length) { + if (!fieldAggs || !fieldAggs.length) { return []; } - return ['Rollup aggregations:'].concat(fieldAggs.map(aggName => { - const agg = allAggs[aggName][field]; - switch(aggName) { - case 'date_histogram': - return `${aggName} (interval: ${agg.interval}, ${agg.delay ? `delay: ${agg.delay},` : ''} ${agg.time_zone})`; - break; - case 'histogram': - return `${aggName} (interval: ${agg.interval})`; - default: - return aggName; - } - })); - } - - areScriptedFieldsEnabled = (indexPattern) => { + return ['Rollup aggregations:'].concat( + fieldAggs.map(aggName => { + const agg = allAggs[aggName][field]; + switch (aggName) { + case 'date_histogram': + return `${aggName} (interval: ${agg.interval}, ${ + agg.delay ? `delay: ${agg.delay},` : '' + } ${agg.time_zone})`; + break; + case 'histogram': + return `${aggName} (interval: ${agg.interval})`; + default: + return aggName; + } + }) + ); + }; + + areScriptedFieldsEnabled = indexPattern => { return !isRollup(indexPattern); - } + }; } diff --git a/x-pack/legacy/plugins/rollup/public/services/api.js b/x-pack/legacy/plugins/rollup/public/services/api.js index 59b26c4592848..ae9e8756c7efc 100644 --- a/x-pack/legacy/plugins/rollup/public/services/api.js +++ b/x-pack/legacy/plugins/rollup/public/services/api.js @@ -3,15 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import chrome from 'ui/chrome'; let httpClient; -export const setHttpClient = (client) => { +export const setHttpClient = client => { httpClient = client; }; -const apiPrefix = chrome.addBasePath('/api/rollup'); export async function getRollupIndices() { - const response = await httpClient.get(`${apiPrefix}/indices`); - return response.data || {}; + const response = await httpClient.get('/api/rollup/indices'); + return response || {}; } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss index d36a587b9257f..5c35e9a23b8a1 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss @@ -1,5 +1,4 @@ @import '@elastic/eui/src/components/header/variables'; -@import '@elastic/eui/src/components/panel/mixins'; @import 'mixins'; @@ -16,7 +15,6 @@ } } - .prfDevTool__page { height: 100%; display: flex; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss index 09bcddef02cc3..6e2ef4a129397 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss @@ -1,4 +1,3 @@ - @include euiBreakpoint('xs', 's') { .prfDevTool__shardDetailsWrapper { flex-direction: column; @@ -34,7 +33,8 @@ font-size: $euiSize; color: $euiColorMediumShade; } - h1, p { + h1, + p { cursor: default; user-select: none; } @@ -44,7 +44,7 @@ } } -@include euiPanel('prfDevTool__main'); +@include euiPanel('.prfDevTool__main'); @include euiBreakpoint('xs', 's') { .prfDevTool__container { @@ -59,4 +59,3 @@ margin: $euiSize 0; } } - diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index fc83dc64aafab..1d798a4a2bc40 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -110,7 +110,8 @@ export const security = (kibana) => new kibana.Plugin({ } watchStatusAndLicenseToInitialize(server.plugins.xpack_main, this, async () => { - if (securityPlugin.__legacyCompat.license.getFeatures().allowRbac) { + const xpackInfo = server.plugins.xpack_main.info; + if (xpackInfo.isAvailable() && xpackInfo.feature('security').isEnabled()) { await securityPlugin.__legacyCompat.registerPrivilegesWithCluster(); } }); diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index fca4a13db8cb5..cf9fffc6a1455 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -29,6 +29,7 @@ import { DEFAULT_SIGNALS_INDEX_KEY, } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; +import { initServerWithKibana } from './server/kibana.index'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const siem = (kibana: any) => { @@ -136,45 +137,29 @@ export const siem = (kibana: any) => { mappings: savedObjectMappings, }, init(server: Server) { - const { - config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, - newPlatform, - plugins, - route, - savedObjects, - } = server; - - const { - env, - coreContext: { logger }, - setup, - } = newPlatform; - const initializerContext = { logger, env }; + const { config, newPlatform, plugins, route } = server; + const { coreContext, env, setup } = newPlatform; + const initializerContext = { ...coreContext, env } as PluginInitializerContext; const serverFacade = { config, - getInjectedUiAppVars, - indexPatternsServiceFactory, - injectUiAppVars, plugins: { alerting: plugins.alerting, - xpack_main: plugins.xpack_main, + elasticsearch: plugins.elasticsearch, spaces: plugins.spaces, }, route: route.bind(server), - savedObjects, }; + // @ts-ignore-next-line: setup.plugins is too loosely typed + plugin(initializerContext).setup(setup.core, setup.plugins); - plugin(initializerContext as PluginInitializerContext).setup( - setup.core, - setup.plugins, - serverFacade - ); + initServerWithKibana(initializerContext, serverFacade); }, config(Joi: Root) { + // See x-pack/plugins/siem/server/config.ts if you're adding another + // value where the configuration has to be duplicated at the moment. + // When we move over to the new platform completely this will be + // removed and only server/config.ts should be used. return Joi.object() .keys({ enabled: Joi.boolean().default(true), diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 526bb52c61afa..d149a33d3b20d 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -100,7 +100,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "success": "#7de2d1", "warning": "#ffce7a", }, - "euiCardGraphicHeight": "40px", + "euiCardBottomNodeHeight": "40px", "euiCardSelectButtonBackgrounds": Object { "danger": "#4d1f1f", "ghost": "#98a2b3", @@ -116,7 +116,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "text": "#7de2d1", }, "euiCardSpacing": "16px", - "euiCardTitleSize": "18px", "euiCheckBoxSize": "16px", "euiCodeBlockAdditionBackgroundColor": "#144212", "euiCodeBlockAdditionColor": "#e6e1dc", @@ -290,7 +289,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiNavDrawerWidthCollapsed": "48px", "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", - "euiPanelBorderColor": "#000000", "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", @@ -334,6 +332,7 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiScrollBar": "16px", "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", + "euiSelectableListItemPadding": "4px 12px", "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap index ddeb701c3ee31..f637c15553823 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -100,7 +100,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "success": "#7de2d1", "warning": "#ffce7a", }, - "euiCardGraphicHeight": "40px", + "euiCardBottomNodeHeight": "40px", "euiCardSelectButtonBackgrounds": Object { "danger": "#4d1f1f", "ghost": "#98a2b3", @@ -116,7 +116,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "text": "#7de2d1", }, "euiCardSpacing": "16px", - "euiCardTitleSize": "18px", "euiCheckBoxSize": "16px", "euiCodeBlockAdditionBackgroundColor": "#144212", "euiCodeBlockAdditionColor": "#e6e1dc", @@ -290,7 +289,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiNavDrawerWidthCollapsed": "48px", "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", - "euiPanelBorderColor": "#000000", "euiPanelPaddingModifiers": Object { "paddingLarge": "24px", "paddingMedium": "16px", @@ -334,6 +332,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiScrollBar": "16px", "euiScrollBarCorner": "6px", "euiSelectableListItemBorder": "1px solid #202128", + "euiSelectableListItemPadding": "4px 12px", "euiShadowColor": "#000000", "euiShadowColorLarge": "#000000", "euiSize": "16px", diff --git a/x-pack/legacy/plugins/siem/server/graphql/index.ts b/x-pack/legacy/plugins/siem/server/graphql/index.ts index 901d27295479a..4e28605f6c0b2 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/index.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/index.ts @@ -55,15 +55,3 @@ export const schemas = [ uncommonProcessesSchema, whoAmISchema, ]; - -// The types from graphql-tools/src/mock.ts 'any' based. I add slightly -// stricter types here, but these should go away when graphql-tools using something -// other than "any" in the future for its types. -// https://github.com/apollographql/graphql-tools/blob/master/src/mock.ts#L406 -export interface SiemContext { - req: { - payload: { - operationName: string; - }; - }; -} diff --git a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts index 790b3aaa83095..24589822f0250 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts @@ -9,7 +9,6 @@ import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { IndexFields } from '../../lib/index_fields'; import { SourceStatus } from '../../lib/source_status'; import { QuerySourceResolver } from '../sources/resolvers'; -import { FrameworkFieldsRequest } from '../../lib/index_fields/types'; export type SourceStatusIndicesExistResolver = ChildResolverOf< AppResolverOf, @@ -47,7 +46,7 @@ export const createSourceStatusResolvers = (libs: { ) { return []; } - return libs.fields.getFields(req as FrameworkFieldsRequest, args.defaultIndex); + return libs.fields.getFields(req, args.defaultIndex); }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index d6db1c4ef5bb9..bb0958b32fa19 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -4,16 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; - -import { Logger, EnvironmentMode } from 'src/core/server'; -import { initServer } from './init_server'; -import { compose } from './lib/compose/kibana'; -import { - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, -} from './saved_objects'; +import { PluginInitializerContext } from 'src/core/server'; import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type'; import { isAlertExecutor } from './lib/detection_engine/alerts/types'; @@ -30,74 +21,33 @@ import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_ind const APP_ID = 'siem'; -export const initServerWithKibana = ( - kbnServer: ServerFacade, - logger: Logger, - mode: EnvironmentMode -) => { - if (kbnServer.plugins.alerting != null) { - const version = kbnServer.config().get('pkg.version'); +export const initServerWithKibana = (context: PluginInitializerContext, __legacy: ServerFacade) => { + const logger = context.logger.get('plugins', APP_ID); + const version = context.env.packageInfo.version; + + if (__legacy.plugins.alerting != null) { const type = rulesAlertType({ logger, version }); if (isAlertExecutor(type)) { - kbnServer.plugins.alerting.setup.registerType(type); + __legacy.plugins.alerting.setup.registerType(type); } } - kbnServer.injectUiAppVars('siem', async () => kbnServer.getInjectedUiAppVars('kibana')); - - const libs = compose(kbnServer, mode); - initServer(libs); // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules // All REST rule creation, deletion, updating, etc... - createRulesRoute(kbnServer); - readRulesRoute(kbnServer); - updateRulesRoute(kbnServer); - deleteRulesRoute(kbnServer); - findRulesRoute(kbnServer); + createRulesRoute(__legacy); + readRulesRoute(__legacy); + updateRulesRoute(__legacy); + deleteRulesRoute(__legacy); + findRulesRoute(__legacy); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status // Example usage can be found in siem/server/lib/detection_engine/scripts/signals - setSignalsStatusRoute(kbnServer); + setSignalsStatusRoute(__legacy); // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces - createIndexRoute(kbnServer); - readIndexRoute(kbnServer); - deleteIndexRoute(kbnServer); - - const xpackMainPlugin = kbnServer.plugins.xpack_main; - xpackMainPlugin.registerFeature({ - id: APP_ID, - name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { - defaultMessage: 'SIEM', - }), - icon: 'securityAnalyticsApp', - navLinkId: 'siem', - app: ['siem', 'kibana'], - catalogue: ['siem'], - privileges: { - all: { - api: ['siem'], - savedObject: { - all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], - read: ['config'], - }, - ui: ['show'], - }, - read: { - api: ['siem'], - savedObject: { - all: [], - read: [ - 'config', - noteSavedObjectType, - pinnedEventSavedObjectType, - timelineSavedObjectType, - ], - }, - ui: ['show'], - }, - }, - }); + createIndexRoute(__legacy); + readIndexRoute(__legacy); + deleteIndexRoute(__legacy); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts index 6e0c5e98206e4..9d5ac6db7bbb7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/siem/server/lib/compose/kibana.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EnvironmentMode } from 'src/core/server'; -import { ServerFacade } from '../../types'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { Anomalies } from '../anomalies'; import { ElasticsearchAnomaliesAdapter } from '../anomalies/elasticsearch_adapter'; import { Authentications } from '../authentications'; import { ElasticsearchAuthenticationAdapter } from '../authentications/elasticsearch_adapter'; -import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; import { ElasticsearchEventsAdapter, Events } from '../events'; import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter'; import { ElasticsearchHostsAdapter, Hosts } from '../hosts'; @@ -28,21 +26,20 @@ import { Overview } from '../overview'; import { ElasticsearchOverviewAdapter } from '../overview/elasticsearch_adapter'; import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status'; import { ConfigurationSourcesAdapter, Sources } from '../sources'; -import { AppBackendLibs, AppDomainLibs, Configuration } from '../types'; +import { AppBackendLibs, AppDomainLibs } from '../types'; import { ElasticsearchUncommonProcessesAdapter, UncommonProcesses } from '../uncommon_processes'; import { Note } from '../note/saved_object'; import { PinnedEvent } from '../pinned_event/saved_object'; import { Timeline } from '../timeline/saved_object'; -export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackendLibs { - const configuration = new KibanaConfigurationAdapter(server); - const framework = new KibanaBackendFrameworkAdapter(server, mode); - const sources = new Sources(new ConfigurationSourcesAdapter(configuration)); +export function compose(core: CoreSetup, env: PluginInitializerContext['env']): AppBackendLibs { + const framework = new KibanaBackendFrameworkAdapter(core, env); + const sources = new Sources(new ConfigurationSourcesAdapter()); const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework)); - const timeline = new Timeline({ savedObjects: framework.getSavedObjectsService() }); - const note = new Note({ savedObjects: framework.getSavedObjectsService() }); - const pinnedEvent = new PinnedEvent({ savedObjects: framework.getSavedObjectsService() }); + const timeline = new Timeline(); + const note = new Note(); + const pinnedEvent = new PinnedEvent(); const domainLibs: AppDomainLibs = { anomalies: new Anomalies(new ElasticsearchAnomaliesAdapter(framework)), @@ -60,7 +57,6 @@ export function compose(server: ServerFacade, mode: EnvironmentMode): AppBackend }; const libs: AppBackendLibs = { - configuration, framework, sourceStatus, sources, diff --git a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts deleted file mode 100644 index 153fc032883ff..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; - -describe('the KibanaConfigurationAdapter', () => { - test('queries the xpack.siem configuration of the server', async () => { - const mockConfig = { - get: jest.fn(), - }; - - const configurationAdapter = new KibanaConfigurationAdapter({ - config: () => mockConfig, - }); - - await configurationAdapter.get(); - - expect(mockConfig.get).toBeCalledWith('xpack.siem'); - }); - - test('applies the query defaults', async () => { - const configurationAdapter = new KibanaConfigurationAdapter({ - config: () => ({ - get: () => ({}), - }), - }); - - const configuration = await configurationAdapter.get(); - - expect(configuration).toMatchObject({ - query: { - partitionSize: expect.any(Number), - partitionFactor: expect.any(Number), - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts deleted file mode 100644 index 83ddbf5cef0e4..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/configuration/kibana_configuration_adapter.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; - -import { ConfigurationAdapter } from './adapter_types'; - -export class KibanaConfigurationAdapter - implements ConfigurationAdapter { - private readonly server: ServerWithConfig; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(server: any) { - if (!isServerWithConfig(server)) { - throw new Error('Failed to find configuration on server.'); - } - - this.server = server; - } - - public async get() { - const config = this.server.config(); - - if (!isKibanaConfiguration(config)) { - throw new Error('Failed to access configuration of server.'); - } - - const configuration = config.get('xpack.siem') || {}; - const configurationWithDefaults = { - enabled: true, - query: { - partitionSize: 75, - partitionFactor: 1.2, - ...(configuration.query || {}), - }, - sources: {}, - ...configuration, - } as Configuration; - - // we assume this to be the configuration because Kibana would have already validated it - return configurationWithDefaults; - } -} - -interface ServerWithConfig { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config(): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { - return ( - Joi.validate( - maybeServer, - Joi.object({ - config: Joi.func().required(), - }).unknown() - ).error === null - ); -} - -interface KibanaConfiguration { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(key: string): any; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration { - return ( - Joi.validate( - maybeConfiguration, - Joi.object({ - get: Joi.func().required(), - }).unknown() - ).error === null - ); -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md index d82424c1c3bd3..7c22d6334a2d1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md @@ -60,7 +60,7 @@ which will: - Delete any existing alert tasks you have - Delete any existing signal mapping, policies, and template, you might have previously had. - Add the latest signal index and its mappings using your settings from `kibana.dev.yml` environment variable of `xpack.siem.signalsIndex`. -- Posts the sample rule from `rules/root_or_admin_1.json` +- Posts the sample rule from `./rules/queries/query_with_rule_id.json` - The sample rule checks for root or admin every 5 minutes and reports that as a signal if it is a positive hit Now you can run @@ -154,11 +154,12 @@ logging.events: See these two README.md's pages for more references on the alerting and actions API: https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/alerting/README.md https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/actions + ### Signals API To update the status of a signal or group of signals, the following scripts provide an example of how to go about doing so. -` cd x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` +`cd x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` `./signals/put_signal_doc.sh` will post a sample signal doc into the signals index to play with `./signals/set_status_with_id.sh closed` will update the status of the sample signal to closed `./signals/set_status_with_id.sh open` will update the status of the sample signal to open diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index bed466dd9b94f..4c113544e6e21 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -22,7 +22,6 @@ export const sampleRuleAlertParams = ( index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], type: 'query', from: 'now-6m', - tags: ['some fake tag'], to: 'now', severity: 'high', query: 'user.name: root or user.name: admin', @@ -31,7 +30,6 @@ export const sampleRuleAlertParams = ( references: ['http://google.com'], riskScore: riskScore ? riskScore : 50, maxSignals: maxSignals ? maxSignals : 10000, - filter: undefined, filters: undefined, savedId: undefined, meta: undefined, @@ -278,7 +276,7 @@ export const sampleRule = (): Partial => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts index 4418bbc52b57d..4cbf3756f58ac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts @@ -13,7 +13,6 @@ export const createRules = async ({ description, enabled, falsePositives, - filter, from, query, language, @@ -38,7 +37,7 @@ export const createRules = async ({ return alertsClient.create({ data: { name, - tags: [], + tags, alertTypeId: SIGNALS_ID, params: { description, @@ -46,7 +45,6 @@ export const createRules = async ({ index, falsePositives, from, - filter, immutable, query, language, @@ -57,7 +55,6 @@ export const createRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts index c55c99fb291c4..e1d10e2efdefb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts @@ -385,26 +385,9 @@ describe('get_filter', () => { }); describe('getFilter', () => { - test('returns a filter if given a type of filter as is', async () => { - const filter = await getFilter({ - type: 'filter', - filter: { something: '1' }, - filters: undefined, - language: undefined, - query: undefined, - savedId: undefined, - services: servicesMock, - index: ['auditbeat-*'], - }); - expect(filter).toEqual({ - something: '1', - }); - }); - test('returns a query if given a type of query', async () => { const filter = await getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -439,7 +422,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: undefined, query: 'host.name: siem', @@ -454,7 +436,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: undefined, @@ -469,7 +450,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'query', - filter: undefined, filters: undefined, language: 'kuery', query: 'host.name: siem', @@ -483,7 +463,6 @@ describe('get_filter', () => { test('returns a saved query if given a type of query', async () => { const filter = await getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -507,7 +486,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, @@ -522,7 +500,6 @@ describe('get_filter', () => { await expect( getFilter({ type: 'saved_query', - filter: undefined, filters: undefined, language: undefined, query: undefined, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts index 5d3b47ecebfd5..858f3580f57e8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts @@ -42,7 +42,6 @@ export const getQueryFilter = ( interface GetFilterArgs { type: RuleAlertParams['type']; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; language: string | undefined | null; query: string | undefined | null; @@ -52,7 +51,6 @@ interface GetFilterArgs { } export const getFilter = async ({ - filter, filters, index, language, @@ -95,9 +93,6 @@ export const getFilter = async ({ throw new TypeError('savedId parameter should be defined'); } } - case 'filter': { - return filter; - } } return assertUnreachable(type); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts index 577de2ce0d532..9823d8b3b9bea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts @@ -34,7 +34,6 @@ export const rulesAlertType = ({ description: schema.string(), falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), from: schema.string(), - filter: schema.nullable(schema.object({}, { allowUnknowns: true })), ruleId: schema.string(), immutable: schema.boolean({ defaultValue: false }), index: schema.nullable(schema.arrayOf(schema.string())), @@ -47,7 +46,6 @@ export const rulesAlertType = ({ maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), riskScore: schema.number(), severity: schema.string(), - tags: schema.arrayOf(schema.string(), { defaultValue: [] }), threats: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), to: schema.string(), type: schema.string(), @@ -56,7 +54,6 @@ export const rulesAlertType = ({ }, async executor({ alertId, services, params }) { const { - filter, from, ruleId, index, @@ -72,6 +69,7 @@ export const rulesAlertType = ({ // TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522 const savedObject = await services.savedObjectsClient.get('alert', alertId); const name: string = savedObject.attributes.name; + const tags: string[] = savedObject.attributes.tags; const createdBy: string = savedObject.attributes.createdBy; const updatedBy: string = savedObject.attributes.updatedBy; @@ -87,7 +85,6 @@ export const rulesAlertType = ({ const inputIndex = await getInputIndex(services, version, index); const esFilter = await getFilter({ type, - filter, filters, language, query, @@ -106,18 +103,20 @@ export const rulesAlertType = ({ }); try { - logger.debug(`Starting signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); logger.debug( - `[+] Initial search call of signal rule "id: ${alertId}", "ruleId: ${ruleId}"` + `Starting signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); + logger.debug( + `[+] Initial search call of signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); const noReIndexResult = await services.callCluster('search', noReIndex); if (noReIndexResult.hits.total.value !== 0) { logger.info( `Found ${ noReIndexResult.hits.total.value - } signals from the indexes of "${inputIndex.join( + } signals from the indexes of "[${inputIndex.join( ', ' - )}" using signal rule "id: ${alertId}", "ruleId: ${ruleId}", pushing signals to index ${outputIndex}` + )}]" using signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}", pushing signals to index "${outputIndex}"` ); } @@ -128,24 +127,30 @@ export const rulesAlertType = ({ logger, id: alertId, signalsIndex: outputIndex, + filter: esFilter, name, createdBy, updatedBy, interval, enabled, pageSize: searchAfterSize, + tags, }); if (bulkIndexResult) { - logger.debug(`Finished signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.debug( + `Finished signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } else { - logger.error(`Error processing signal rule "id: ${alertId}", "ruleId: ${ruleId}"`); + logger.error( + `Error processing signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + ); } } catch (err) { // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule "id: ${alertId}", "ruleId: ${ruleId}", ${err.message}` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` ); } }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index cb1d59254923b..c9d265ebffacd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -35,7 +35,6 @@ export interface RuleAlertParams { description: string; enabled: boolean; falsePositives: string[]; - filter: Record | undefined | null; filters: PartialFilter[] | undefined | null; from: string; immutable: boolean; @@ -55,7 +54,7 @@ export interface RuleAlertParams { tags: string[]; to: string; threats: ThreatParams[] | undefined | null; - type: 'filter' | 'query' | 'saved_query'; + type: 'query' | 'saved_query'; } export type RuleAlertParamsRest = Omit< @@ -149,7 +148,7 @@ export interface ReadRuleByRuleId { ruleId: string; } -export type RuleTypeParams = Omit; +export type RuleTypeParams = Omit; export type RuleAlertType = Alert & { id: string; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts index d6b828642433d..2eaa05ae2fa6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts @@ -53,7 +53,6 @@ export const updateRules = async ({ savedId, meta, filters, - filter, from, immutable, id, @@ -88,7 +87,6 @@ export const updateRules = async ({ { description, falsePositives, - filter, from, immutable, query, @@ -101,7 +99,6 @@ export const updateRules = async ({ maxSignals, riskScore, severity, - tags, threats, to, type, @@ -114,11 +111,10 @@ export const updateRules = async ({ } else if (!rule.enabled && enabled) { await alertsClient.enable({ id: rule.id }); } - return alertsClient.update({ id: rule.id, data: { - tags: [], + tags: tags != null ? tags : [], name: calculateName({ updatedName: name, originalName: rule.name }), interval: calculateInterval(interval, rule.interval), actions, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index fc50e54e06e4e..41052ab4bbb15 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -68,6 +68,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -102,7 +103,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -131,6 +132,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -174,7 +176,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -202,6 +204,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -244,7 +247,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -270,6 +273,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; @@ -307,7 +311,7 @@ describe('utils', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', enabled: true, @@ -448,6 +452,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -475,6 +480,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -494,6 +500,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(successfulsingleBulkCreate).toEqual(true); }); @@ -513,6 +520,7 @@ describe('utils', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(successfulsingleBulkCreate).toEqual(true); @@ -530,6 +538,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Attempted to search after with empty sort id'); }); @@ -543,6 +552,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }); expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); }); @@ -559,6 +569,7 @@ describe('utils', () => { services: mockService, logger: mockLogger, pageSize: 1, + filter: undefined, }) ).rejects.toThrow('Fake Error'); }); @@ -579,6 +590,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); @@ -629,6 +642,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); @@ -650,6 +665,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -678,6 +695,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); @@ -706,6 +725,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -736,6 +757,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -766,6 +789,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(true); }); @@ -798,6 +823,8 @@ describe('utils', () => { interval: '5m', enabled: true, pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], }); expect(result).toEqual(false); }); @@ -873,7 +900,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -926,7 +953,7 @@ describe('utils', () => { references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', updated_by: 'elastic', - tags: [], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', }, @@ -957,6 +984,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -977,7 +1005,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1007,6 +1035,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1027,7 +1056,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', @@ -1046,6 +1075,7 @@ describe('utils', () => { createdBy: 'elastic', updatedBy: 'elastic', interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], }); const expected: Partial = { created_by: 'elastic', @@ -1066,7 +1096,7 @@ describe('utils', () => { risk_score: 50, rule_id: 'rule-1', severity: 'high', - tags: ['some fake tag'], + tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', type: 'query', updated_by: 'elastic', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index 9dedda6d79839..1787aa3a3081b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -26,6 +26,7 @@ interface BuildRuleParams { createdBy: string; updatedBy: string; interval: string; + tags: string[]; } export const buildRule = ({ @@ -36,6 +37,7 @@ export const buildRule = ({ createdBy, updatedBy, interval, + tags, }: BuildRuleParams): Partial => { return pickBy((value: unknown) => value != null, { id, @@ -47,7 +49,6 @@ export const buildRule = ({ risk_score: ruleParams.riskScore, output_index: ruleParams.outputIndex, description: ruleParams.description, - filter: ruleParams.filter, from: ruleParams.from, immutable: ruleParams.immutable, index: ruleParams.index, @@ -57,7 +58,7 @@ export const buildRule = ({ query: ruleParams.query, references: ruleParams.references, severity: ruleParams.severity, - tags: ruleParams.tags, + tags, type: ruleParams.type, to: ruleParams.to, enabled, @@ -95,6 +96,7 @@ interface BuildBulkBodyParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const buildEventTypeSignal = (doc: SignalSourceHit): object => { @@ -115,6 +117,7 @@ export const buildBulkBody = ({ updatedBy, interval, enabled, + tags, }: BuildBulkBodyParams): SignalHit => { const rule = buildRule({ ruleParams, @@ -124,6 +127,7 @@ export const buildBulkBody = ({ createdBy, updatedBy, interval, + tags, }); const signal = buildSignal(doc, rule); const event = buildEventTypeSignal(doc); @@ -148,6 +152,7 @@ interface SingleBulkCreateParams { updatedBy: string; interval: string; enabled: boolean; + tags: string[]; } export const generateId = ( @@ -173,6 +178,7 @@ export const singleBulkCreate = async ({ updatedBy, interval, enabled, + tags, }: SingleBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -198,7 +204,7 @@ export const singleBulkCreate = async ({ ), }, }, - buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled }), + buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), ]); const time1 = performance.now(); const firstResult: BulkResponse = await services.callCluster('bulk', { @@ -207,7 +213,9 @@ export const singleBulkCreate = async ({ body: bulkBody, }); const time2 = performance.now(); - logger.debug(`individual bulk process time took: ${time2 - time1} milliseconds`); + logger.debug( + `individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds` + ); logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`); if (firstResult.errors) { // go through the response status errors and see what @@ -241,6 +249,7 @@ interface SingleSearchAfterParams { services: AlertServices; logger: Logger; pageSize: number; + filter: unknown; } // utilize search_after for paging results into bulk. @@ -248,6 +257,7 @@ export const singleSearchAfter = async ({ searchAfterSortId, ruleParams, services, + filter, logger, pageSize, }: SingleSearchAfterParams): Promise => { @@ -259,7 +269,7 @@ export const singleSearchAfter = async ({ index: ruleParams.index, from: ruleParams.from, to: ruleParams.to, - filter: ruleParams.filter, + filter, size: pageSize, searchAfterSortId, }); @@ -287,6 +297,8 @@ interface SearchAfterAndBulkCreateParams { interval: string; enabled: boolean; pageSize: number; + filter: unknown; + tags: string[]; } // search_after through documents and re-index using bulk endpoint. @@ -297,12 +309,14 @@ export const searchAfterAndBulkCreate = async ({ logger, id, signalsIndex, + filter, name, createdBy, updatedBy, interval, enabled, pageSize, + tags, }: SearchAfterAndBulkCreateParams): Promise => { if (someResult.hits.hits.length === 0) { return true; @@ -321,6 +335,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); const totalHits = typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; @@ -353,6 +368,7 @@ export const searchAfterAndBulkCreate = async ({ ruleParams, services, logger, + filter, pageSize, // maximum number of docs to receive per search result. }); if (searchAfterResult.hits.hits.length === 0) { @@ -379,6 +395,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + tags, }); logger.debug('finished next bulk index'); } catch (exc) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index c18891c032060..cd8b716221b9b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -38,20 +38,6 @@ export const typicalPayload = (): Partial> = ], }); -export const typicalFilterPayload = (): Partial => ({ - rule_id: 'rule-1', - description: 'Detecting root and admin users', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - name: 'Detect Root/Admin Users', - risk_score: 50, - type: 'filter', - from: 'now-6m', - to: 'now', - severity: 'high', - filter: {}, -}); - export const typicalSetStatusSignalByIdsPayload = (): Partial => ({ signal_ids: ['somefakeid1', 'somefakeid2'], status: 'closed', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts index 4aa57f005445b..b271af2db1e7d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts @@ -103,27 +103,6 @@ describe('create_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResult()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.create.mockResolvedValue(createActionResult()); - alertsClient.create.mockResolvedValue(getResult()); - // Cannot type request with a ServerInjectOptions as the type system complains - // about the property filter involving Hapi types, so I left it off for now - const { language, query, type, ...noType } = typicalPayload(); - const request = { - method: 'POST', - url: DETECTION_ENGINE_RULES_URL, - payload: { - ...noType, - type: 'filter', - filter: {}, - }, - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResult()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts index 31068dac5d23a..a137d54250189 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts @@ -35,7 +35,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -68,7 +67,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = try { const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, finalIndex); if (!indexExists) { return new Boom( @@ -81,7 +80,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = if (ruleId != null) { const rule = await readRules({ alertsClient, ruleId }); if (rule != null) { - return new Boom(`rule_id ${ruleId} already exists`, { statusCode: 409 }); + return new Boom(`rule_id: "${ruleId}" already exists`, { statusCode: 409 }); } } const createdRule = await createRules({ @@ -90,7 +89,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = description, enabled, falsePositives, - filter, from, immutable, query, @@ -120,6 +118,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = }; }; -export const createRulesRoute = (server: ServerFacade) => { +export const createRulesRoute = (server: ServerFacade): void => { server.route(createCreateRulesRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 14a061fd4ccb7..94c42664c281d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -34,10 +34,10 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { - return new Boom(`index ${index} already exists`, { statusCode: 409 }); + return new Boom(`index: "${index}" already exists`, { statusCode: 409 }); } else { const policyExists = await getPolicyExists(callWithRequest, index); if (!policyExists) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts index 89f21bbada939..82fe0f55215fb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -42,10 +42,10 @@ export const createDeleteIndexRoute = (server: ServerFacade): Hapi.ServerRoute = async handler(request: RequestFacade) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (!indexExists) { - return new Boom(`index ${index} does not exist`, { statusCode: 404 }); + return new Boom(`index: "${index}" does not exist`, { statusCode: 404 }); } else { await deleteAllIndex(callWithRequest, `${index}-*`); const policyExists = await getPolicyExists(callWithRequest, index); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts index 87b6ffb985c37..a8c4b7407c448 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts @@ -27,7 +27,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => async handler(request: RequestFacade, headers) { try { const index = getIndex(request, server); - const callWithRequest = callWithRequestFactory(request); + const callWithRequest = callWithRequestFactory(request, server); const indexExists = await getIndexExists(callWithRequest, index); if (indexExists) { // head request is used for if you want to get if the index exists @@ -42,7 +42,7 @@ export const createReadIndexRoute = (server: ServerFacade): Hapi.ServerRoute => if (request.method.toLowerCase() === 'head') { return headers.response().code(404); } else { - return new Boom('An index for this space does not exist', { statusCode: 404 }); + return new Boom('index for this space does not exist', { statusCode: 404 }); } } } catch (err) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts index dd45490c778bf..f5147bc5a8f8b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -141,7 +141,7 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does not validate', () => { + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { expect( createRulesSchema.validate>({ rule_id: 'rule-1', @@ -156,7 +156,7 @@ describe('schemas', () => { index: ['index-1'], interval: '5m', }).error - ).toBeTruthy(); + ).toBeFalsy(); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -227,8 +227,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', risk_score: 50, }).error ).toBeFalsy(); @@ -247,8 +246,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -287,8 +285,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', threats: [ { framework: 'someFramework', @@ -310,84 +307,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('If filter type is set then filter is required', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then query is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as valid', () => { expect( createRulesSchema.validate>({ @@ -509,26 +428,6 @@ describe('schemas', () => { ).toEqual(100); }); - test('filter and filters cannot exist together', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is required when type is saved_query and will not validate without out', () => { expect( createRulesSchema.validate>({ @@ -608,26 +507,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( createRulesSchema.validate>({ @@ -1157,30 +1036,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('You can have an empty query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: '', - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - test('You can omit the query string when filters are present', () => { expect( createRulesSchema.validate & { meta: string }>>({ @@ -1203,29 +1058,6 @@ describe('schemas', () => { }).error ).toBeFalsy(); }); - - test('query string defaults to empty string when present with filters', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).value.query - ).toEqual(''); - }); }); describe('update rules schema', () => { @@ -1556,8 +1388,7 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, + type: 'query', }).error ).toBeFalsy(); }); @@ -1573,82 +1404,11 @@ describe('schemas', () => { name: 'some-name', severity: 'severity', interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then filter is still not required', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', + type: 'query', }).error ).toBeFalsy(); }); - test('If filter type is set then query is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('allows references to be sent as a valid value to update with', () => { expect( updateRulesSchema.validate>({ @@ -1758,24 +1518,6 @@ describe('schemas', () => { ).toBeTruthy(); }); - test('filter and filters cannot exist together', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - test('saved_id is not required when type is saved_query and will validate without it', () => { expect( updateRulesSchema.validate>({ @@ -1827,24 +1569,6 @@ describe('schemas', () => { ).toBeFalsy(); }); - test('saved_query type cannot have filter with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - test('language validates with kuery', () => { expect( updateRulesSchema.validate>({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index d1a7a01c72415..6ed6fdd2577d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -11,7 +11,6 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; const description = Joi.string(); const enabled = Joi.boolean(); const false_positives = Joi.array().items(Joi.string()); -const filter = Joi.object(); const filters = Joi.array(); const from = Joi.string(); const immutable = Joi.boolean(); @@ -34,7 +33,7 @@ const risk_score = Joi.number() const severity = Joi.string(); const status = Joi.string().valid('open', 'closed'); const to = Joi.string(); -const type = Joi.string().valid('filter', 'query', 'saved_query'); +const type = Joi.string().valid('query', 'saved_query'); const queryFilter = Joi.string(); const references = Joi.array() .items(Joi.string()) @@ -85,46 +84,14 @@ export const createRulesSchema = Joi.object({ description: description.required(), enabled: enabled.default(true), false_positives: false_positives.default([]), - filter: filter.when('type', { is: 'filter', then: Joi.required(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from: from.required(), rule_id, immutable: immutable.default(false), index, interval: interval.default('5m'), - query: Joi.when('type', { - is: 'query', - then: Joi.when('filters', { - is: Joi.exist(), - then: query - .optional() - .allow('') - .default(''), - otherwise: Joi.required(), - }), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.required(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow('').default(''), + language: language.default('kuery'), output_index, saved_id: saved_id.when('type', { is: 'saved_query', @@ -147,46 +114,17 @@ export const updateRulesSchema = Joi.object({ description, enabled, false_positives, - filter: filter.when('type', { is: 'filter', then: Joi.optional(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), + filters, from, rule_id, id, immutable, index, interval, - query: Joi.when('type', { - is: 'query', - then: query.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), + query: query.allow(''), + language, output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.optional(), - otherwise: Joi.forbidden(), - }), + saved_id, meta, risk_score, max_signals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index b4152256fbb53..99af43ce51a12 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -27,7 +27,7 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute async handler(request: SignalsRequest, headers) { const { signal_ids: signalIds, query, status } = request.payload; const index = getIndex(request, server); - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); let queryObject; if (signalIds) { queryObject = { ids: { values: signalIds } }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts index d03d68417dd5d..dfa1275a6b26b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts @@ -20,7 +20,6 @@ import { getUpdateRequest, typicalPayload, getFindResultWithSingleHit, - typicalFilterPayload, } from './__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; @@ -119,20 +118,6 @@ describe('update_rules', () => { expect(statusCode).toBe(200); }); - test('returns 200 if type is filter', async () => { - alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); - alertsClient.get.mockResolvedValue(getResult()); - actionsClient.update.mockResolvedValue(updateActionResult()); - alertsClient.update.mockResolvedValue(getResult()); - const request: ServerInjectOptions = { - method: 'PUT', - url: DETECTION_ENGINE_RULES_URL, - payload: typicalFilterPayload(), - }; - const { statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - }); - test('returns 400 if type is not filter or kql', async () => { alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); alertsClient.get.mockResolvedValue(getResult()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts index 156756698435f..943c41fd6dea6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts @@ -30,7 +30,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, false_positives: falsePositives, - filter, from, immutable, query, @@ -68,7 +67,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { description, enabled, falsePositives, - filter, from, immutable, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index d20b4d213e9cc..4663ea357f259 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -341,22 +341,22 @@ describe('utils', () => { describe('getIdError', () => { test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { const boom = getIdError({ id: '123', ruleId: undefined }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about id not being found if only id is defined and ruleId is null', () => { const boom = getIdError({ id: '123', ruleId: null }); - expect(boom.message).toEqual('id of 123 not found'); + expect(boom.message).toEqual('id: "123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { const boom = getIdError({ id: undefined, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { const boom = getIdError({ id: null, ruleId: 'rule-id-123' }); - expect(boom.message).toEqual('rule_id of rule-id-123 not found'); + expect(boom.message).toEqual('rule_id: "rule-id-123" not found'); }); test('outputs message about both being not defined when both are undefined', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 4c5a5a6af93de..6df4174e628b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -18,9 +18,9 @@ export const getIdError = ({ ruleId: string | undefined | null; }) => { if (id != null) { - return new Boom(`id of ${id} not found`, { statusCode: 404 }); + return new Boom(`id: "${id}" not found`, { statusCode: 404 }); } else if (ruleId != null) { - return new Boom(`rule_id of ${ruleId} not found`, { statusCode: 404 }); + return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); } else { return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); } @@ -34,7 +34,6 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial { - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); +export const callWithRequestFactory = (request: RequestFacade, server: ServerFacade) => { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); return (endpoint: string, params: T, options?: U) => { return callWithRequest(request, endpoint, params, options); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh index 34b6208947c57..52d49979a072b 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/find_rule_by_filter.sh @@ -13,6 +13,7 @@ FILTER=${1:-'alert.attributes.enabled:%20true'} # Example: ./find_rule_by_filter.sh "alert.attributes.enabled:%20true" # Example: ./find_rule_by_filter.sh "alert.attributes.name:%20Detect*" +# Example: ./find_rule_by_filter.sh "alert.attributes.tags:tag_1" # The %20 is just an encoded space that is typical of URL's. # Table of them for testing if needed: https://www.w3schools.com/tags/ref_urlencode.asp curl -s -k \ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh index 591cf7625e2e3..432045634ba7b 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/post_rule.sh @@ -10,11 +10,11 @@ set -e ./check_env_variables.sh # Uses a default if no argument is specified -RULES=(${@:-./rules/root_or_admin_1.json}) +RULES=(${@:-./rules/queries/query_with_rule_id.json}) # Example: ./post_rule.sh -# Example: ./post_rule.sh ./rules/root_or_admin_1.json -# Example glob: ./post_rule.sh ./rules/* +# Example: ./post_rule.sh ./rules/queries/query_with_rule_id.json +# Example glob: ./post_rule.sh ./rules/queries/* for RULE in "${RULES[@]}" do { [ -e "$RULE" ] || continue diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md new file mode 100644 index 0000000000000..04db6c683d726 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/README.md @@ -0,0 +1,21 @@ +These are example POST rules that only have queries and and no filters in them. + +Every single json file should have the field: + +```sh +"type": "query" +``` + +set which is what designates it as a type of query. + +To post all of them to see in the UI, with the scripts folder as your current working directory. + +```sh +./post_rule.sh ./rules/queries/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/queries/.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json new file mode 100644 index 0000000000000..38b3ed9f74696 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is disabled", + "description": "Example query which will is disabled and will not run after being posted", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json new file mode 100644 index 0000000000000..681d66e16d0ba --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json @@ -0,0 +1,11 @@ +{ + "name": "Query which is immutable", + "description": "Example query which is immutable", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "immutable": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json new file mode 100644 index 0000000000000..ed8849831a479 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json @@ -0,0 +1,11 @@ +{ + "name": "Query with the language set to lucene", + "description": "Query with the language set to lucene", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "language": "lucene" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json similarity index 74% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json index 9ca4c32631334..721a172ce55d7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_threats.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json @@ -1,17 +1,12 @@ { - "rule_id": "rule-1", - "description": "Detecting root and admin users", - "index": ["auditbeat-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], - "interval": "5s", - "name": "Detect Root/Admin Users", - "severity": "high", + "name": "Query which has Mitre Attack Data", + "description": "Example query which has Mitre Attack Data as threats", "risk_score": 1, + "severity": "high", "type": "query", - "from": "now-6s", + "from": "now-6m", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], "threats": [ { "framework": "MITRE ATT&CK", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json new file mode 100644 index 0000000000000..adb96ba398f5e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Query with all possible fields filled out", + "description": "Kitchen Sink (everything) query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json similarity index 62% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json index eb7f2ae03b64b..c754ab73ea21e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_5.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json @@ -1,15 +1,13 @@ { - "rule_id": "rule-5", - "risk_score": 5, - "description": "Detecting root and admin users over 24 hours on windows", - "interval": "5m", - "name": "Detect Root/Admin Users", + "name": "Query with two filters", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, "severity": "high", "type": "query", "from": "now-24h", "to": "now", "query": "user.name: root or user.name: admin", - "language": "kuery", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json new file mode 100644 index 0000000000000..f9f5bf854e45c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json @@ -0,0 +1,17 @@ +{ + "name": "Query which has extra meta data", + "description": "Query which has extra meta data", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "meta": { + "whatever-you-want": { + "store-stateful-stuff-for-ui": { + "or-anything-else": true + } + } + } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json new file mode 100644 index 0000000000000..e4da196007527 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Query with a rule id", + "description": "Query with a rule_id that acts like an external id", + "rule_id": "query-rule-id", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json new file mode 100644 index 0000000000000..954f5942180d6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json @@ -0,0 +1,12 @@ +{ + "name": "Query with a set of tags", + "description": "Query with a set a tags", + "rule_id": "tags-query", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "tags": ["tag_1", "tag_2"] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json new file mode 100644 index 0000000000000..61e68f886ffe7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json @@ -0,0 +1,18 @@ +{ + "name": "Simplest Filter", + "description": "Simplest filter with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json new file mode 100644 index 0000000000000..e812b031a28fd --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Query", + "description": "Simplest query with the least amount of fields required", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json deleted file mode 100644 index b00a5929d9ef1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json deleted file mode 100644 index 657439104e306..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_10.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json deleted file mode 100644 index 137cf7eedbccf..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-2", - "risk_score": 2, - "description": "Detecting root and admin users over a long period of time", - "interval": "24h", - "name": "Detect Root/Admin Users over a long period of time", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json deleted file mode 100644 index b9160c95621ee..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_3.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-3", - "risk_score": 3, - "description": "Detecting root and admin users as an empty set", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-16y", - "to": "now-15y", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json deleted file mode 100644 index 364e7f00c9571..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-4", - "risk_score": 4, - "description": "Detecting root and admin users with lucene", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "lucene", - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json deleted file mode 100644 index de24263c6af5c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_8.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rule_id": "rule-8", - "risk_score": 8, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json deleted file mode 100644 index 9bf2b1abf5f90..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_9.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "rule_id": "rule-9", - "risk_score": 9, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "enabled": false, - "tags": ["tag_1", "tag_2"], - "false_positives": ["false_1", "false_2"], - "immutable": true, - "references": ["http://www.example.com", "https://ww.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json deleted file mode 100644 index 2381e9e259c07..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9998.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "must": [], - "filter": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "host.name": "siem-windows" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "match_phrase": { - "winlog.event_id": { - "query": "100" - } - } - }, - { - "match_phrase": { - "agent.hostname": { - "query": "siem-windows" - } - } - } - ], - "should": [], - "must_not": [] - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json deleted file mode 100644 index ee8fe1fc93fb3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_filter_9999.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "rule_id": "rule-9999", - "risk_score": 100, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "filter", - "from": "now-6m", - "to": "now", - "filter": { - "bool": { - "should": [ - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "root" - } - } - ], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "match_phrase": { - "user.name": "admin" - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json deleted file mode 100644 index ed8f2e5745bea..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "rule_id": "rule-meta-data", - "risk_score": 1, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery", - "references": ["http://www.example.com", "https://ww.example.com"], - "meta": { - "anything_i_want": { - "total_meta_for_ui_needs": true - } - } -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json deleted file mode 100644 index 721644acd989d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-1", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json deleted file mode 100644 index b733b6bb8c592..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_2.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "saved-query-2", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-2", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json deleted file mode 100644 index df1b37f19bf29..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_saved_query_3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "rule_id": "saved-query-3", - "risk_score": 5, - "description": "Detecting root and admin users", - "interval": "5m", - "name": "Detect Root/Admin Users", - "severity": "high", - "type": "saved_query", - "from": "now-6m", - "to": "now", - "saved_id": "test-saveid-3" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json deleted file mode 100644 index 09ddfb1c34a92..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 98, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json deleted file mode 100644 index 8a3c765519ef3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_update_2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "rule_id": "rule-1", - "risk_score": 78, - "description": "Changed Description of only detecting root user", - "interval": "50m", - "name": "A different name", - "severity": "high", - "type": "query", - "false_positives": ["false_update_1", "false_update_2"], - "from": "now-6m", - "immutable": true, - "tags": ["some other tag for you"], - "to": "now-5m", - "query": "user.name: root", - "language": "kuery", - "references": ["https://update1.example.com", "https://update2.example.com"] -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md new file mode 100644 index 0000000000000..bdd4f33c35c78 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/README.md @@ -0,0 +1,32 @@ +These are example POST rules that have the ability to use saved query id's and optionally +queries and filters. If you only use a saved query id, then detection engine relies on that +saved query id existing or it will throw errors if the user deletes the saved query id. If you +add a saved query id along side with a filter and/or query then it will try to use the saved query +id first and if that fails it will fall back on the provided filter and/or query. + +Every single json file should have the field: + +```sh +"type": "saved_query" +``` + +set which is what designates it as a type of saved_query + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/saved_queries/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/saved_queries/.json +``` + +If the saved_id does not exist and you do not provide a query and/or filter then expect to see this +in your kibana console logging: + +```sh +server log [11:48:33.331] [error][task_manager] Task alerting:siem.signals "fedc2390-1858-11ea-9184-15f04d7099dc" failed: Error: Saved object [query/test-saved-id] not found +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json new file mode 100644 index 0000000000000..0e0be24c00207 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query that uses a rule-id", + "rule_id": "simplest-saved-query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json new file mode 100644 index 0000000000000..c4aaf4aae93c2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -0,0 +1,81 @@ +{ + "name": "Saved Query with all possible fields filled out", + "description": "Kitchen Sink (everything) saved query that has all possible fields filled out", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "saved-query-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "saved_query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json new file mode 100644 index 0000000000000..55f95e9644b8b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json @@ -0,0 +1,19 @@ +{ + "name": "Simplest saved id with filters", + "description": "Simplest saved_id with a filter for a fallback if the saved_id is not found", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json new file mode 100644 index 0000000000000..ee37c4cb784d1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json @@ -0,0 +1,11 @@ +{ + "name": "Simplest Saved Query", + "description": "Simplest saved query with a fallback of a query", + "risk_score": 1, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "query": "user.name: root or user.name: admin", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json new file mode 100644 index 0000000000000..19801e7a98ac2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json @@ -0,0 +1,26 @@ +{ + "name": "Query with filter", + "description": "A KQL Query with a two filters", + "rule_id": "query-with-two-filters", + "risk_score": 15, + "severity": "high", + "type": "query", + "from": "now-24h", + "to": "now", + "query": "user.name: root or user.name: admin", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json new file mode 100644 index 0000000000000..a3dbf0f1b09af --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json @@ -0,0 +1,10 @@ +{ + "name": "Simplest Saved Query", + "description": "Using a saved_id called test-saved-id but does not provide a pre-defined filter or query", + "risk_score": 5, + "severity": "high", + "type": "saved_query", + "from": "now-6m", + "to": "now", + "saved_id": "test-saved-id" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md new file mode 100644 index 0000000000000..8b6508c64dc5c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md @@ -0,0 +1,18 @@ +These are example POST rules that are more either Kibana UI test cases, mis-use test cases, +any generic e2e based test cases for testing different scenarios. Normally you would not +use these type of rule based messages when writing pure REST API calls. These messages are +more of what you would see "behind the scenes" when you are using Kibana UI which can +create rules with additional "meta" data or other implementation details that aren't really +a concern for a regular REST API user. + +To post all of them to see in the UI, with the scripts folder as your current working directory: + +```sh +./post_rule.sh ./rules/test_cases/*.json +``` + +To post only one at a time: + +```sh +./post_rule.sh ./rules/test_cases/.json +``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_with_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/filter_without_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json similarity index 80% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json index 81ec19a4fd0ef..ced59c3ef2c7b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_7.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-7", + "name": "Query Filter With UI Meta Data With Lucene as a language", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-lucene", "risk_score": 7, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json similarity index 82% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json index 94f30bc9f92df..21fe7f7e2557a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/root_or_admin_6.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json @@ -1,9 +1,9 @@ { - "rule_id": "rule-6", + "name": "Query Filter With UI Meta Data", + "description": "A Query Filter that has lots of UI meta data state in the filters which goes back by 24 hours", + "rule_id": "query-filter-ui-kuery", "risk_score": 6, - "description": "Detecting root and admin users", "interval": "5m", - "name": "Detect Root/Admin Users", "severity": "high", "type": "query", "from": "now-24h", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json new file mode 100644 index 0000000000000..607dcd963780c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json @@ -0,0 +1,36 @@ +{ + "type": "saved_query", + "index": ["auditbeat-*", "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*"], + "language": "kuery", + "filters": [ + { + "meta": { + "alias": null, + "negate": false, + "disabled": false, + "type": "phrase", + "key": "source.ip", + "params": { "query": "127.0.0.1" } + }, + "query": { + "match": { "source.ip": { "query": "127.0.0.1", "type": "phrase" } } + }, + "$state": { "store": "appState" } + } + ], + "output_index": ".siem-signals-default", + "query": "", + "saved_id": "User's IP", + "false_positives": [], + "references": [], + "risk_score": 21, + "name": "Signal Maker 5000", + "description": "It's the Garrett", + "severity": "low", + "tags": ["Spong"], + "interval": "5m", + "from": "now-300s", + "enabled": true, + "to": "now", + "meta": { "from": "now-300s" } +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md new file mode 100644 index 0000000000000..97a5d31bb0133 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/README.md @@ -0,0 +1,25 @@ +These are example PUT rules to see how to update various parts of the rules. +You either have to use the id, or you have to use the rule_id in order to update +the rules. rule_id acts as an external_id where you can update rules across different +Kibana systems where id acts as a normal server generated id which is not normally shared +across different Kibana systems. + +The only thing you cannot update is the `rule_id` or regular `id` of the system. If `rule_id` +is incorrect then you have to delete the rule completely and re-initialize it with the +correct `rule_id` + +First add all the examples from queries like so: + +```sh +./post_rule.sh ./rules/queries/*.json +``` + +Then to selectively update a rule add the file of your choosing to update: + +```sh +./update_rule.sh ./rules/updates/.json +``` + +Take note that the ones with "id" must be changed to a GUID that only you know about through +a `./find_rules.sh`. For example to grab a GUID id off of the first found record that exists +you can do: `./find_rules.sh | jq '.data[0].id'` and then replace the id in `updates/simplest_update_risk_score_by_id.json` with that particular id to watch it happen. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json new file mode 100644 index 0000000000000..a94558143882b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/disable_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": false +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json new file mode 100644 index 0000000000000..bfe7c7f546fc3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/enabled_rule.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json new file mode 100644 index 0000000000000..00966ddba7c7a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_id.json @@ -0,0 +1,4 @@ +{ + "id": "ade31ba8-dc49-4c18-b7f4-370b35df5f57", + "risk_score": 38 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json new file mode 100644 index 0000000000000..ad3c78183297d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_update_risk_score_by_rule_id.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "risk_score": 98 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json new file mode 100644 index 0000000000000..56c9f151dc712 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/simplest_updated_name.json @@ -0,0 +1,4 @@ +{ + "rule_id": "query-rule-id", + "name": "Changes only the name to this new value" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json new file mode 100644 index 0000000000000..285e9c94a76d9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -0,0 +1,80 @@ +{ + "name": "Updates a query with all possible fields that can be updated", + "description": "Kitchen Sink (everything) query that has all possible fields filled out.", + "false_positives": [ + "https://www.example.com/some-article-about-a-false-positive", + "some text string about why another condition could be a false positive" + ], + "rule_id": "rule-id-everything", + "filters": [ + { + "query": { + "match_phrase": { + "host.name": "siem-windows" + } + } + }, + { + "exists": { + "field": "host.hostname" + } + } + ], + "enabled": false, + "immutable": true, + "index": ["auditbeat-*", "filebeat-*"], + "interval": "5m", + "query": "user.name: root or user.name: admin", + "output_index": ".siem-signals-default", + "meta": { + "anything_you_want_ui_related_or_otherwise": { + "as_deep_structured_as_you_need": { + "any_data_type": {} + } + } + }, + "language": "kuery", + "risk_score": 1, + "max_signals": 100, + "tags": ["tag 1", "tag 2", "any tag you want"], + "to": "now", + "from": "now-6m", + "severity": "high", + "type": "query", + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "techniques": [ + { + "id": "T1499", + "name": "endpoint denial of service", + "reference": "https://attack.mitre.org/techniques/T1499/" + } + ] + }, + { + "framework": "Some other Framework you want", + "tactic": { + "id": "some-other-id", + "name": "Some other name", + "reference": "https://example.com" + }, + "techniques": [ + { + "id": "some-other-id", + "name": "some other technique name", + "reference": "https://example.com" + } + ] + } + ], + "references": [ + "http://www.example.com/some-article-about-attack", + "Some plain text string here explaining why this is a valid thing to look out for" + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json new file mode 100644 index 0000000000000..be833105792c6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_tags.json @@ -0,0 +1,4 @@ +{ + "rule_id": "tags-query", + "tags": ["tag_3"] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json deleted file mode 100644 index a43398bd6876a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/watch_longmont.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rule_id": "rule-longmont", - "risk_score": 5, - "description": "Detect Longmont activity", - "interval": "24h", - "name": "Detect Longmont activity", - "severity": "high", - "type": "query", - "from": "now-1y", - "to": "now", - "query": "user.name: root or user.name: admin", - "language": "kuery" -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh index 8e1abc7045602..aa22db965664a 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/update_rule.sh @@ -10,11 +10,11 @@ set -e ./check_env_variables.sh # Uses a default if no argument is specified -RULES=(${@:-./rules/root_or_admin_update_1.json}) +RULES=(${@:-./rules/updates/simplest_updated_name.json}) # Example: ./update_rule.sh -# Example: ./update_rule.sh ./rules/root_or_admin_1.json -# Example glob: ./post_rule.sh ./rules/* +# Example: ./update_rule.sh ./rules/updates/simplest_updated_name.json +# Example glob: ./post_rule.sh ./rules/updates/* for RULE in "${RULES[@]}" do { [ -e "$RULE" ] || continue diff --git a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts index dbeba598639b1..b1f0c3c4a3a18 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/elasticsearch_adapter.test.ts @@ -521,10 +521,8 @@ describe('events elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 88c46b18d515a..38fec7fc05a3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GenericParams } from 'elasticsearch'; import * as GraphiQL from 'apollo-server-module-graphiql'; -import Boom from 'boom'; -import { ResponseToolkit } from 'hapi'; -import { EnvironmentMode } from 'kibana/public'; import { GraphQLSchema } from 'graphql'; import { runHttpQuery } from 'apollo-server-core'; -import { ServerFacade, RequestFacade } from '../../types'; +import { schema as configSchema } from '@kbn/config-schema'; +import { + CoreSetup, + IRouter, + KibanaResponseFactory, + RequestHandlerContext, + PluginInitializerContext, +} from 'src/core/server'; +import { IndexPatternsFetcher } from '../../../../../../../src/plugins/data/server'; +import { RequestFacade } from '../../types'; import { FrameworkAdapter, @@ -21,125 +26,119 @@ import { WrappableRequest, } from './types'; -interface CallWithRequestParams extends GenericParams { - max_concurrent_shard_requests?: number; -} - export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; - public envMode: EnvironmentMode; + private isProductionMode: boolean; + private router: IRouter; - constructor(private server: ServerFacade, mode: EnvironmentMode) { - this.version = server.config().get('pkg.version'); - this.envMode = mode; + constructor(core: CoreSetup, env: PluginInitializerContext['env']) { + this.version = env.packageInfo.version; + this.isProductionMode = env.mode.prod; + this.router = core.http.createRouter(); } public async callWithRequest( req: FrameworkRequest, endpoint: string, - params: CallWithRequestParams, // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...rest: any[] + params: Record ) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen'); + const { elasticsearch, uiSettings } = req.context.core; + const includeFrozen = await uiSettings.client.get('search:includeFrozen'); const maxConcurrentShardRequests = endpoint === 'msearch' - ? await internalRequest.getUiSettingsService().get('courier:maxConcurrentShardRequests') + ? await uiSettings.client.get('courier:maxConcurrentShardRequests') : 0; - const fields = await callWithRequest( - internalRequest, - endpoint, - { - ...params, - ignore_throttled: !includeFrozen, - ...(maxConcurrentShardRequests > 0 - ? { max_concurrent_shard_requests: maxConcurrentShardRequests } - : {}), - }, - ...rest - ); - return fields; - } - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, + return elasticsearch.dataClient.callAsCurrentUser(endpoint, { + ...params, + ignore_throttled: !includeFrozen, + ...(maxConcurrentShardRequests > 0 + ? { max_concurrent_shard_requests: maxConcurrentShardRequests } + : {}), }); } public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { - this.server.route({ - options: { - tags: ['access:siem'], + this.router.post( + { + path: routePath, + validate: { + body: configSchema.object({ + operationName: configSchema.string(), + query: configSchema.string(), + variables: configSchema.object({}, { allowUnknowns: true }), + }), + }, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { try { - const query = - request.method === 'post' - ? (request.payload as Record) // eslint-disable-line @typescript-eslint/no-explicit-any - : (request.query as Record); // eslint-disable-line @typescript-eslint/no-explicit-any - const gqlResponse = await runHttpQuery([request], { - method: request.method.toUpperCase(), + method: 'POST', options: (req: RequestFacade) => ({ - context: { req: wrapRequest(req) }, + context: { req: wrapRequest(req, context) }, schema, }), - - query, + query: request.body, }); - return h.response(gqlResponse).type('application/json'); + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, + }); } catch (error) { - if ('HttpQueryError' !== error.name) { - const queryError = Boom.boomify(error); - - queryError.output.payload.message = error.message; - - return queryError; - } - - if (error.isGraphQLError === true) { - return h - .response(error.message) - .code(error.statusCode) - .type('application/json'); - } + return this.handleError(error, response); + } + } + ); - const genericError = new Boom(error.message, { statusCode: error.statusCode }); + if (!this.isProductionMode) { + this.router.get( + { + path: routePath, + validate: { query: configSchema.object({}, { allowUnknowns: true }) }, + options: { + tags: ['access:siem'], + }, + }, + async (context, request, response) => { + try { + const { query } = request; + const gqlResponse = await runHttpQuery([request], { + method: 'GET', + options: (req: RequestFacade) => ({ + context: { req: wrapRequest(req, context) }, + schema, + }), + query, + }); - if (error.headers) { - Object.keys(error.headers).forEach(header => { - genericError.output.headers[header] = error.headers[header]; + return response.ok({ + body: gqlResponse, + headers: { + 'content-type': 'application/json', + }, }); + } catch (error) { + return this.handleError(error, response); } - - // Boom hides the error when status code is 500 - genericError.output.payload.message = error.message; - - throw genericError; } - }, - method: ['GET', 'POST'], - path: routePath, - vhost: undefined, - }); - - if (!this.envMode.prod) { - this.server.route({ - options: { - tags: ['access:siem'], + ); + + this.router.get( + { + path: `${routePath}/graphiql`, + validate: false, + options: { + tags: ['access:siem'], + }, }, - handler: async (request: RequestFacade, h: ResponseToolkit) => { + async (context, request, response) => { const graphiqlString = await GraphiQL.resolveGraphiQLString( request.query, { @@ -149,42 +148,60 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { request ); - return h.response(graphiqlString).type('text/html'); + return response.ok({ + body: graphiqlString, + headers: { + 'content-type': 'text/html', + }, + }); + } + ); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private handleError(error: any, response: KibanaResponseFactory) { + if (error.name !== 'HttpQueryError') { + return response.internalError({ + body: error.message, + headers: { + 'content-type': 'application/json', }, - method: 'GET', - path: `${routePath}/graphiql`, }); } - } - public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { - return this.server.indexPatternsServiceFactory({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => { - const fieldCaps = await this.callWithRequest( - request, - method, - { ...args, allowNoIndices: true } as GenericParams, - ...rest - ); - return fieldCaps; + return response.customError({ + statusCode: error.statusCode, + body: error.message, + headers: { + 'content-type': 'application/json', + ...error.headers, }, }); } - public getSavedObjectsService() { - return this.server.savedObjects; + public getIndexPatternsService(request: FrameworkRequest): FrameworkIndexPatternsService { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callCluster = async (endpoint: string, params?: Record) => + this.callWithRequest(request, endpoint, { + ...params, + allowNoIndices: true, + }); + + return new IndexPatternsFetcher(callCluster); } } export function wrapRequest( - req: InternalRequest + req: InternalRequest, + context: RequestHandlerContext ): FrameworkRequest { const { auth, params, payload, query } = req; return { [internalFrameworkRequest]: req, auth, + context, params, payload, query, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts index ffa113a18c36c..dd31ed9fcaf5f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/types.ts @@ -7,8 +7,8 @@ import { IndicesGetMappingParams } from 'elasticsearch'; import { GraphQLSchema } from 'graphql'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; +import { RequestHandlerContext } from 'src/core/server'; import { ESQuery } from '../../../common/typed_json'; import { PaginationInput, @@ -25,7 +25,6 @@ export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); export interface FrameworkAdapter { version: string; - exposeStaticDir(urlPath: string, dir: string): void; registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; callWithRequest( req: FrameworkRequest, @@ -37,27 +36,17 @@ export interface FrameworkAdapter { method: 'msearch', options?: object ): Promise>; - callWithRequest( - req: FrameworkRequest, - method: 'indices.existsAlias', - options?: object - ): Promise; callWithRequest( req: FrameworkRequest, method: 'indices.getMapping', options?: IndicesGetMappingParams // eslint-disable-line ): Promise; - callWithRequest( - req: FrameworkRequest, - method: 'indices.getAlias' | 'indices.get', // eslint-disable-line - options?: object - ): Promise; getIndexPatternsService(req: FrameworkRequest): FrameworkIndexPatternsService; - getSavedObjectsService(): Legacy.SavedObjectsService; } export interface FrameworkRequest { [internalFrameworkRequest]: InternalRequest; + context: RequestHandlerContext; payload: InternalRequest['payload']; params: InternalRequest['params']; query: InternalRequest['query']; @@ -132,22 +121,6 @@ export interface FrameworkIndexPatternsService { }): Promise; } -interface Alias { - settings: { - index: { - uuid: string; - }; - }; -} - -export interface DatabaseGetIndicesResponse { - [indexName: string]: { - aliases: { - [aliasName: string]: Alias; - }; - }; -} - export interface RequestBasicOptions { sourceConfiguration: SourceConfiguration; timerange: TimerangeInput; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts index fc5c037dfbb5f..0d698f1e19213 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts @@ -161,10 +161,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -184,10 +182,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); @@ -207,10 +203,8 @@ describe('hosts elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest })); diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts index 291345b6befd3..48b9750ddd949 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts @@ -14,22 +14,20 @@ import { hasDocumentation, IndexAlias, } from '../../utils/beat_schema'; -import { FrameworkAdapter } from '../framework'; -import { FieldsAdapter, IndexFieldDescriptor, FrameworkFieldsRequest } from './types'; +import { FrameworkAdapter, FrameworkRequest } from '../framework'; +import { FieldsAdapter, IndexFieldDescriptor } from './types'; type IndexesAliasIndices = Record; export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexFields( - request: FrameworkFieldsRequest, - indices: string[] - ): Promise { + public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise { const indexPatternsService = this.framework.getIndexPatternsService(request); const indexesAliasIndices: IndexesAliasIndices = indices.reduce( (accumulator: IndexesAliasIndices, indice: string) => { - const key: string = getIndexAlias(request.payload.variables.defaultIndex, indice); + const key = getIndexAlias(indices, indice); + if (get(key, accumulator)) { accumulator[key] = [...accumulator[key], indice]; } else { diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts index 325aaa4fa4339..a3ea8548bddc2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/index.ts @@ -6,16 +6,14 @@ import { IndexField } from '../../graphql/types'; -import { FieldsAdapter, FrameworkFieldsRequest } from './types'; +import { FieldsAdapter } from './types'; +import { FrameworkRequest } from '../framework'; export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter'; export class IndexFields { constructor(private readonly adapter: FieldsAdapter) {} - public async getFields( - request: FrameworkFieldsRequest, - defaultIndex: string[] - ): Promise { + public async getFields(request: FrameworkRequest, defaultIndex: string[]): Promise { return this.adapter.getIndexFields(request, defaultIndex); } } diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts index 719b9e8d6e18e..0c894c6980a31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/types.ts @@ -6,20 +6,9 @@ import { IndexField } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; -import { RequestFacade } from '../../types'; - -type IndexFieldsRequest = RequestFacade & { - payload: { - variables: { - defaultIndex: string[]; - }; - }; -}; - -export type FrameworkFieldsRequest = FrameworkRequest; export interface FieldsAdapter { - getIndexFields(req: FrameworkFieldsRequest, indices: string[]): Promise; + getIndexFields(req: FrameworkRequest, indices: string[]): Promise; } export interface IndexFieldDescriptor { diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts index 1c388a1808c70..4a179073852b0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_hosts/elasticsearch_adapter.test.ts @@ -55,10 +55,8 @@ describe('getKpiHosts', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; @@ -171,10 +169,8 @@ describe('getKpiHostDetails', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiHosts: ElasticsearchKpiHostsAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts index 9c192eddf6f8a..11d007f591fac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/kpi_network/elastic_adapter.test.ts @@ -50,10 +50,8 @@ describe('Network Kpi elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; let EsKpiNetwork: ElasticsearchKpiNetworkAdapter; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts index 542a2a0108a9a..eeea4bec2fb25 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts @@ -37,9 +37,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -65,10 +63,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -107,9 +103,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ @@ -140,10 +134,8 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -165,9 +157,7 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), registerGraphQLEndpoint: jest.fn(), }; jest.doMock('../framework', () => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts index 30e9ace2c0f19..e5fce811e560f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/note/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import uuid from 'uuid'; @@ -15,7 +14,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { PageInfoNote, ResponseNote, @@ -31,20 +29,11 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class Note { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deleteNote(request: FrameworkRequest, noteIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( - noteIds.map(noteId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, noteId) - ) + noteIds.map(noteId => savedObjectsClient.delete(noteSavedObjectType, noteId)) ); } @@ -55,11 +44,11 @@ export class Note { searchFields: ['timelineId'], }; const notesToBeDeleted = await this.getAllSavedNote(request, options); + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( notesToBeDeleted.notes.map(note => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(noteSavedObjectType, note.noteId) + savedObjectsClient.delete(noteSavedObjectType, note.noteId) ) ); } @@ -119,17 +108,17 @@ export class Note { note: SavedNote ): Promise { try { + const savedObjectsClient = request.context.core.savedObjects.client; + if (noteId == null) { const timelineVersionSavedObject = note.timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); note.timelineId = timelineResult.savedObjectId; return timelineResult.version; @@ -141,12 +130,10 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - noteSavedObjectType, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) - ), + await savedObjectsClient.create( + noteSavedObjectType, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ), }; @@ -157,16 +144,14 @@ export class Note { code: 200, message: 'success', note: convertSavedObjectToSavedNote( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - noteSavedObjectType, - noteId, - pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ) + await savedObjectsClient.update( + noteSavedObjectType, + noteId, + pickSavedNote(noteId, note, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ) ), }; } catch (err) { @@ -189,20 +174,14 @@ export class Note { } private async getSavedNote(request: FrameworkRequest, NoteId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(noteSavedObjectType, NoteId); return convertSavedObjectToSavedNote(savedObject); } private async getAllSavedNote(request: FrameworkRequest, options: SavedObjectsFindOptions) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return { diff --git a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts index 75c460f0807b4..d904219c76531 100644 --- a/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/overview/elastic_adapter.test.ts @@ -38,10 +38,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -74,10 +72,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -114,10 +110,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, @@ -155,10 +149,8 @@ describe('Siem Overview elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; jest.doMock('../framework', () => ({ callWithRequest: mockCallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts index 882a139eb739a..177eb05d8539d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/pinned_event/saved_object.ts @@ -6,7 +6,6 @@ import { failure } from 'io-ts/lib/PathReporter'; import { RequestAuth } from 'hapi'; -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; @@ -14,7 +13,6 @@ import { SavedObjectsFindOptions } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { Pick3 } from '../../../common/utility_types'; import { FrameworkRequest, internalFrameworkRequest } from '../framework'; import { PinnedEventSavedObject, @@ -27,24 +25,18 @@ import { pickSavedTimeline } from '../timeline/pick_saved_timeline'; import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline'; export class PinnedEvent { - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) {} - public async deletePinnedEventOnTimeline(request: FrameworkRequest, pinnedEventIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( pinnedEventIds.map(pinnedEventId => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEventId) ) ); } public async deleteAllPinnedEventsOnTimeline(request: FrameworkRequest, timelineId: string) { + const savedObjectsClient = request.context.core.savedObjects.client; const options: SavedObjectsFindOptions = { type: pinnedEventSavedObjectType, search: timelineId, @@ -53,9 +45,7 @@ export class PinnedEvent { const pinnedEventToBeDeleted = await this.getAllSavedPinnedEvents(request, options); await Promise.all( pinnedEventToBeDeleted.map(pinnedEvent => - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) + savedObjectsClient.delete(pinnedEventSavedObjectType, pinnedEvent.pinnedEventId) ) ); } @@ -103,18 +93,18 @@ export class PinnedEvent { eventId: string, timelineId: string | null ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (pinnedEventId == null) { const timelineVersionSavedObject = timelineId == null ? await (async () => { const timelineResult = convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null) + ) ); timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign return timelineResult.version; @@ -133,16 +123,14 @@ export class PinnedEvent { }; // create Pinned Event on Timeline return convertSavedObjectToSavedPinnedEvent( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - pinnedEventSavedObjectType, - pickSavedPinnedEvent( - pinnedEventId, - savedPinnedEvent, - request[internalFrameworkRequest].auth || null - ) - ), + await savedObjectsClient.create( + pinnedEventSavedObjectType, + pickSavedPinnedEvent( + pinnedEventId, + savedPinnedEvent, + request[internalFrameworkRequest].auth || null + ) + ), timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined ); } @@ -177,10 +165,7 @@ export class PinnedEvent { } private async getSavedPinnedEvent(request: FrameworkRequest, pinnedEventId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(pinnedEventSavedObjectType, pinnedEventId); return convertSavedObjectToSavedPinnedEvent(savedObject); @@ -190,10 +175,7 @@ export class PinnedEvent { request: FrameworkRequest, options: SavedObjectsFindOptions ) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObjects = await savedObjectsClient.find(options); return savedObjects.saved_objects.map(savedObject => diff --git a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts index 3aeb137688594..17d203918d825 100644 --- a/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/source_status/elasticsearch_adapter.ts @@ -4,38 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DatabaseGetIndicesResponse, FrameworkAdapter, FrameworkRequest } from '../framework'; - +import { FrameworkAdapter, FrameworkRequest } from '../framework'; import { SourceStatusAdapter } from './index'; export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { constructor(private readonly framework: FrameworkAdapter) {} - public async getIndexNames(request: FrameworkRequest, aliasName: string | string[]) { - const indexMaps = await Promise.all([ - this.framework - .callWithRequest(request, 'indices.getAlias', { - name: aliasName, - filterPath: '*.settings.index.uuid', // to keep the response size as small as possible - }) - .catch(withDefaultIfNotFound({})), - this.framework - .callWithRequest(request, 'indices.get', { - index: aliasName, - filterPath: '*.settings.index.uuid', // to keep the response size as small as possible - }) - .catch(withDefaultIfNotFound({})), - ]); - return indexMaps.reduce( - (indexNames, indexMap) => [...indexNames, ...Object.keys(indexMap)], - [] as string[] - ); - } - - public async hasAlias(request: FrameworkRequest, aliasName: string): Promise { - return this.framework.callWithRequest(request, 'indices.existsAlias', { - name: aliasName, - }); - } public async hasIndices(request: FrameworkRequest, indexNames: string | string[]) { return this.framework @@ -56,13 +29,3 @@ export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter { ); } } - -const withDefaultIfNotFound = (defaultValue: DefaultValue) => ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: any -): DefaultValue => { - if (error && error.status === 404) { - return defaultValue; - } - throw error; -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts index 42ff7cca19964..aa620f6cf2590 100644 --- a/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts +++ b/x-pack/legacy/plugins/siem/server/lib/sources/configuration.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { ConfigurationAdapter } from '../configuration'; +import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter'; import { SourcesAdapter, SourceConfiguration } from './index'; import { PartialSourceConfigurations } from './types'; @@ -15,7 +16,11 @@ interface ConfigurationWithSources { export class ConfigurationSourcesAdapter implements SourcesAdapter { private readonly configuration: ConfigurationAdapter; - constructor(configuration: ConfigurationAdapter) { + constructor( + configuration: ConfigurationAdapter< + ConfigurationWithSources + > = new InmemoryConfigurationAdapter({ sources: {} }) + ) { this.configuration = configuration; } diff --git a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts index 515920ace28d8..04c1584f1204d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/timeline/saved_object.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { getOr } from 'lodash/fp'; import { SavedObjectsFindOptions } from 'src/core/server'; -import { Pick3 } from '../../../common/utility_types'; import { ResponseTimeline, PageInfoTimeline, @@ -34,17 +32,8 @@ interface ResponseTimelines { } export class Timeline { - private readonly note: Note; - private readonly pinnedEvent: PinnedEvent; - constructor( - private readonly libs: { - savedObjects: Pick & - Pick3; - } - ) { - this.note = new Note({ savedObjects: this.libs.savedObjects }); - this.pinnedEvent = new PinnedEvent({ savedObjects: this.libs.savedObjects }); - } + private readonly note = new Note(); + private readonly pinnedEvent = new PinnedEvent(); public async getTimeline( request: FrameworkRequest, @@ -149,6 +138,8 @@ export class Timeline { version: string | null, timeline: SavedTimeline ): Promise { + const savedObjectsClient = request.context.core.savedObjects.client; + try { if (timelineId == null) { // Create new timeline @@ -156,40 +147,33 @@ export class Timeline { code: 200, message: 'success', timeline: convertSavedObjectToSavedTimeline( - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .create( - timelineSavedObjectType, - pickSavedTimeline( - timelineId, - timeline, - request[internalFrameworkRequest].auth || null - ) + await savedObjectsClient.create( + timelineSavedObjectType, + pickSavedTimeline( + timelineId, + timeline, + request[internalFrameworkRequest].auth || null ) + ) ), }; } // Update Timeline - await this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .update( - timelineSavedObjectType, - timelineId, - pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), - { - version: version || undefined, - } - ); + await savedObjectsClient.update( + timelineSavedObjectType, + timelineId, + pickSavedTimeline(timelineId, timeline, request[internalFrameworkRequest].auth || null), + { + version: version || undefined, + } + ); return { code: 200, message: 'success', timeline: await this.getSavedTimeline(request, timelineId), }; } catch (err) { - if ( - timelineId != null && - this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err) - ) { + if (timelineId != null && savedObjectsClient.errors.isConflictError(err)) { return { code: 409, message: err.message, @@ -212,12 +196,12 @@ export class Timeline { } public async deleteTimeline(request: FrameworkRequest, timelineIds: string[]) { + const savedObjectsClient = request.context.core.savedObjects.client; + await Promise.all( timelineIds.map(timelineId => Promise.all([ - this.libs.savedObjects - .getScopedSavedObjectsClient(request[internalFrameworkRequest]) - .delete(timelineSavedObjectType, timelineId), + savedObjectsClient.delete(timelineSavedObjectType, timelineId), this.note.deleteNoteByTimelineId(request, timelineId), this.pinnedEvent.deleteAllPinnedEventsOnTimeline(request, timelineId), ]) @@ -226,10 +210,7 @@ export class Timeline { } private async getBasicSavedTimeline(request: FrameworkRequest, timelineId: string) { - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); return convertSavedObjectToSavedTimeline(savedObject); @@ -238,10 +219,7 @@ export class Timeline { private async getSavedTimeline(request: FrameworkRequest, timelineId: string) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); - + const savedObjectsClient = request.context.core.savedObjects.client; const savedObject = await savedObjectsClient.get(timelineSavedObjectType, timelineId); const timelineSaveObject = convertSavedObjectToSavedTimeline(savedObject); const timelineWithNotesAndPinnedEvents = await Promise.all([ @@ -257,10 +235,7 @@ export class Timeline { private async getAllSavedTimeline(request: FrameworkRequest, options: SavedObjectsFindOptions) { const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth); - - const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient( - request[internalFrameworkRequest] - ); + const savedObjectsClient = request.context.core.savedObjects.client; if (options.searchFields != null && options.searchFields.includes('favorite.keySearch')) { options.search = `${options.search != null ? options.search : ''} ${ userName != null ? convertStringToBase64(userName) : null diff --git a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts index 0ba0dfcb516fd..32a5c72215dda 100644 --- a/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/tls/elasticsearch_adapter.test.ts @@ -24,10 +24,8 @@ describe('elasticsearch_adapter', () => { const mockFramework: FrameworkAdapter = { version: 'mock', callWithRequest: mockCallWithRequest, - exposeStaticDir: jest.fn(), registerGraphQLEndpoint: jest.fn(), getIndexPatternsService: jest.fn(), - getSavedObjectsService: jest.fn(), }; beforeAll(async () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index c53805dc95fe7..e97a07e276dcf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ConfigType as Configuration } from '../../../../../plugins/siem/server'; import { Anomalies } from './anomalies'; import { Authentications } from './authentications'; -import { ConfigurationAdapter } from './configuration'; import { Events } from './events'; import { FrameworkAdapter, FrameworkRequest } from './framework'; import { Hosts } from './hosts'; @@ -17,7 +17,7 @@ import { KpiNetwork } from './kpi_network'; import { Network } from './network'; import { Overview } from './overview'; import { SourceStatus } from './source_status'; -import { Sources, SourceConfiguration } from './sources'; +import { Sources } from './sources'; import { UncommonProcesses } from './uncommon_processes'; import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; @@ -43,7 +43,6 @@ export interface AppDomainLibs { } export interface AppBackendLibs extends AppDomainLibs { - configuration: ConfigurationAdapter; framework: FrameworkAdapter; sources: Sources; sourceStatus: SourceStatus; @@ -52,15 +51,6 @@ export interface AppBackendLibs extends AppDomainLibs { pinnedEvent: PinnedEvent; } -export interface Configuration { - enabled: boolean; - query: { - partitionSize: number; - partitionFactor: number; - }; - sources: Record; -} - export interface SiemContext { req: FrameworkRequest; } diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 9a799a90efc65..533b6c23088ec 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -4,27 +4,71 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, EnvironmentMode, PluginInitializerContext, Logger } from 'src/core/server'; -import { ServerFacade } from './types'; -import { initServerWithKibana } from './kibana.index'; +import { i18n } from '@kbn/i18n'; +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server'; +import { initServer } from './init_server'; +import { compose } from './lib/compose/kibana'; +import { + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, +} from './saved_objects'; + +export interface PluginsSetup { + features: FeaturesSetupContract; +} export class Plugin { - name = 'siem'; - private mode: EnvironmentMode; - private logger: Logger; + readonly name = 'siem'; + private readonly logger: Logger; + private context: PluginInitializerContext; - constructor({ env, logger }: PluginInitializerContext) { - this.logger = logger.get('plugins', this.name); - this.mode = env.mode; + constructor(context: PluginInitializerContext) { + this.context = context; + this.logger = context.logger.get('plugins', this.name); - this.logger.info('NP plugin initialized'); + this.logger.debug('Shim plugin initialized'); } - public setup(core: CoreSetup, dependencies: {}, __legacy: ServerFacade) { - this.logger.info('NP plugin setup'); + public setup(core: CoreSetup, plugins: PluginsSetup) { + this.logger.debug('Shim plugin setup'); - initServerWithKibana(__legacy, this.logger, this.mode); + plugins.features.registerFeature({ + id: this.name, + name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', { + defaultMessage: 'SIEM', + }), + icon: 'securityAnalyticsApp', + navLinkId: 'siem', + app: ['siem', 'kibana'], + catalogue: ['siem'], + privileges: { + all: { + api: ['siem'], + savedObject: { + all: [noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType], + read: ['config'], + }, + ui: ['show'], + }, + read: { + api: ['siem'], + savedObject: { + all: [], + read: [ + 'config', + noteSavedObjectType, + pinnedEventSavedObjectType, + timelineSavedObjectType, + ], + }, + ui: ['show'], + }, + }, + }); - this.logger.info('NP plugin setup complete'); + const libs = compose(core, this.context.env); + initServer(libs); } } diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts index ad19872b7a75d..989aaf2b6eeea 100644 --- a/x-pack/legacy/plugins/siem/server/types.ts +++ b/x-pack/legacy/plugins/siem/server/types.ts @@ -8,29 +8,21 @@ import { Legacy } from 'kibana'; export interface ServerFacade { config: Legacy.Server['config']; - getInjectedUiAppVars: Legacy.Server['getInjectedUiAppVars']; - indexPatternsServiceFactory: Legacy.Server['indexPatternsServiceFactory']; - injectUiAppVars: Legacy.Server['injectUiAppVars']; plugins: { alerting?: Legacy.Server['plugins']['alerting']; + elasticsearch: Legacy.Server['plugins']['elasticsearch']; spaces: Legacy.Server['plugins']['spaces']; - xpack_main: Legacy.Server['plugins']['xpack_main']; }; route: Legacy.Server['route']; - savedObjects: Legacy.Server['savedObjects']; } export interface RequestFacade { auth: Legacy.Request['auth']; getAlertsClient?: Legacy.Request['getAlertsClient']; getActionsClient?: Legacy.Request['getActionsClient']; - getUiSettingsService: Legacy.Request['getUiSettingsService']; headers: Legacy.Request['headers']; method: Legacy.Request['method']; params: Legacy.Request['params']; payload: unknown; query: Legacy.Request['query']; - server: { - plugins: { elasticsearch: Legacy.Request['server']['plugins']['elasticsearch'] }; - }; } diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index 4ed1937ebf782..e0db5f360f0f6 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; +} from '../../../../../../../src/legacy/core_plugins/management/public'; import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../spaces_manager'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts index 7517fa48ad8b8..0352902072790 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts index 7eddf3f4891e5..8807489157d71 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts index c1672e65326aa..67d34960ed98e 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts @@ -5,7 +5,7 @@ */ import { EventEmitter } from 'events'; import { kfetch } from 'ui/kfetch'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss index 04bc51067d9df..887495a231485 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcConfirmDeleteModal { - max-width: $euiFormMaxWidth + ($euiSizeL*2); + max-width: $euiFormMaxWidth + ($euiSizeL * 2); } diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx index f461100db01db..c30792b23e3ac 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx @@ -6,6 +6,7 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -18,6 +19,11 @@ import { spacesManagerMock } from '../../../../lib/mocks'; import { SpacesManager } from '../../../../lib'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +jest.mock('../../../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ + setup: mockManagementPlugin.createSetupContract(), + start: mockManagementPlugin.createStartContract(), +})); + interface SetupOpts { mockSpaces?: Space[]; returnBeforeSpacesLoad?: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx index 7461edcff10e9..1de5a10977f83 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx @@ -21,12 +21,12 @@ import { import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { - SavedObjectsManagementRecord, - processImportResponse, ProcessedImportResponse, -} from 'ui/management/saved_objects_management'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; + processImportResponse, +} from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { SpacesManager } from '../../../../lib'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx index f8d6fdf85205e..5853bebe3c669 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx @@ -5,10 +5,10 @@ */ import React, { Fragment } from 'react'; -import { ProcessedImportResponse } from 'ui/management/saved_objects_management'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx index 1b712e84d4a05..b04c9598559b3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx @@ -5,10 +5,6 @@ */ import React, { Fragment } from 'react'; -import { - ProcessedImportResponse, - SavedObjectsManagementRecord, -} from 'ui/management/saved_objects_management'; import { EuiSpacer, EuiText, @@ -17,6 +13,8 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { summarizeCopyResult } from '../../../../lib/copy_saved_objects_to_space'; import { Space } from '../../../../../common/model/space'; import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx index b27be4d1715e8..f71be12276be5 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; import { SpaceAvatar } from '../../../../components'; import { Space } from '../../../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx index 43639641d541c..66ec38331c89a 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx @@ -5,10 +5,10 @@ */ import React from 'react'; -import { SavedObjectsManagementRecord } from 'ui/management/saved_objects_management'; import { SummarizedCopyToSpaceResult } from 'plugins/spaces/lib/copy_saved_objects_to_space'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx index 179665ed11111..f659154c910f1 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx @@ -12,9 +12,9 @@ import { PAGE_TITLE_COMPONENT, registerSettingsComponent, } from 'ui/management'; -import { SavedObjectsManagementActionRegistry } from 'ui/management/saved_objects_management'; // @ts-ignore import routes from 'ui/routes'; +import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; import { SpacesManager } from '../../lib'; import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; import { AdvancedSettingsTitle } from './components/advanced_settings_title'; @@ -54,8 +54,8 @@ routes.defaults(/\/management/, { ); // This route resolve function executes any time the management screen is loaded, and we want to ensure // that this action is only registered once. - if (!SavedObjectsManagementActionRegistry.has(action.id)) { - SavedObjectsManagementActionRegistry.register(action); + if (!managementSetup.savedObjects.registry.has(action.id)) { + managementSetup.savedObjects.registry.register(action); } // Customize Advanced Settings diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss index 3fd2f91b25f4a..cfa2fd650e83b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss +++ b/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/form/variables'; - .spcSpaceSelector { @include kibanaFullScreenGraphics; } @@ -27,7 +25,6 @@ @include euiBottomShadowMedium; } - .spcSpaceSelector__searchHolder { width: $euiFormMaxWidth; // make sure it's as wide as our default form element width max-width: 100%; diff --git a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts index 1facc761d6379..589803b33e11c 100644 --- a/x-pack/legacy/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/legacy/plugins/transform/common/utils/object_utils.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - // This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. // It splits the attribute key string and uses reduce with an idx check to access nested attributes. export const getNestedProperty = ( @@ -13,5 +11,5 @@ export const getNestedProperty = ( accessor: string, defaultValue?: any ) => { - return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue; + return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue; }; diff --git a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts index 955dc831e05e2..433616e422802 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/transform_stats.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; - import { TransformId } from './transform'; import { TransformListRow } from './transform_list'; @@ -78,8 +76,7 @@ export function getTransformProgress(item: TransformListRow) { return 100; } - const progress = idx(item, _ => _.stats.checkpointing.next.checkpoint_progress.percent_complete); - + const progress = item?.stats?.checkpointing?.next?.checkpoint_progress?.percent_complete; return progress !== undefined ? Math.round(progress) : undefined; } diff --git a/x-pack/package.json b/x-pack/package.json index 075a7867b4720..a92839eadefa4 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -176,14 +176,13 @@ "@babel/runtime": "^7.5.5", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "16.1.0", + "@elastic/eui": "17.0.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", "@elastic/numeral": "2.3.3", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", - "@kbn/elastic-idx": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/ui-framework": "1.0.0", diff --git a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts index 888565cd7e0ff..c18c071ca7675 100644 --- a/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts +++ b/x-pack/plugins/security/server/authorization/register_privileges_with_cluster.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IClusterClient, Logger } from '../../../../../target/types/core/server'; +import { IClusterClient, Logger } from '../../../../../src/core/server'; import { RawKibanaPrivileges } from '../../common/model'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; diff --git a/x-pack/plugins/siem/kibana.json b/x-pack/plugins/siem/kibana.json new file mode 100644 index 0000000000000..2bc33b87a1b43 --- /dev/null +++ b/x-pack/plugins/siem/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "siem", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "siem"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/siem/server/config.ts b/x-pack/plugins/siem/server/config.ts new file mode 100644 index 0000000000000..456646cc825f3 --- /dev/null +++ b/x-pack/plugins/siem/server/config.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { + SIGNALS_INDEX_KEY, + DEFAULT_SIGNALS_INDEX, +} from '../../../legacy/plugins/siem/common/constants'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + [SIGNALS_INDEX_KEY]: schema.string({ defaultValue: DEFAULT_SIGNALS_INDEX }), +}); + +export const createConfig$ = (context: PluginInitializerContext) => + context.config.create>(); + +export type ConfigType = ReturnType extends Observable + ? T + : ReturnType; diff --git a/x-pack/plugins/siem/server/index.ts b/x-pack/plugins/siem/server/index.ts new file mode 100644 index 0000000000000..c675be691b47e --- /dev/null +++ b/x-pack/plugins/siem/server/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { Plugin } from './plugin'; +import { configSchema, ConfigType } from './config'; + +export const plugin = (context: PluginInitializerContext) => { + return new Plugin(context); +}; + +export const config = { schema: configSchema }; + +export { ConfigType }; diff --git a/x-pack/plugins/siem/server/plugin.ts b/x-pack/plugins/siem/server/plugin.ts new file mode 100644 index 0000000000000..866f4d7575e2f --- /dev/null +++ b/x-pack/plugins/siem/server/plugin.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; + +import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; +import { createConfig$, ConfigType } from './config'; + +export class Plugin { + readonly name = 'siem'; + private readonly logger: Logger; + // @ts-ignore-next-line TODO(rylnd): use it or lose it + private readonly config$: Observable; + + constructor(context: PluginInitializerContext) { + const { logger } = context; + this.logger = logger.get(); + this.logger.debug('plugin initialized'); + + this.config$ = createConfig$(context); + } + + public setup(core: CoreSetup, plugins: {}) { + this.logger.debug('plugin setup'); + } + + public start() { + this.logger.debug('plugin started'); + } + + public stop() { + this.logger.debug('plugin stopped'); + } +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9b9a842689fd4..d51332c65aa54 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "データに接続", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", + "management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", + "management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", + "management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", "common.ui.management.nav.menu": "管理メニュー", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", @@ -12047,23 +12047,7 @@ "visTypeMarkdown.params.fontSizeLabel": "ポイント単位のベースフォントサイズです。", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "アクションタイプ \"{id}\" は登録されていません。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "アクションタイプ \"{id}\" は既に登録されています。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "アクション「{actionId}」メールの送信中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "アクション「{actionId}」データのインデックス中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "アクション {actionId} の構成で設定されていないため、インデックスパラメーターの設定が必要です", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: {errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: ステータス {status}、後程再試行してください", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "PagerDuty アクション「{actionId}」イベントの投稿中にエラーが発生: 予期せぬステータス {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "アクション「{actionId}」メッセージのログ記録宙にエラーが発生: {errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました: {retryString} に再試行してください: {message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "アクション「{id}」Slack メッセージの投稿中にエラーが発生しました。後程再試行してください", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "Slack から予期せぬテキスト応答", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "無効な応答:Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。{retryString} で後程再試行してください: {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "無効な応答: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました。後程再試行してください", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "到達不能な Web フック: Web フックアクション「{id}」リモート Web フックの呼び出し中にエラーが発生しました: {message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "リモートウェブフックにアクセスできません。アドレスが正しいことを確認してください。", "xpack.actions.builtin.webhook.webhookConfigurationError": "Web フックアクションの構成中にエラーが発生: {message}", "xpack.actions.urlWhitelistConfigurationError": "ターゲット {field} 「{value}」は Kibana のホワイトリストに登録されていません", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "パネルに追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 91dc516eebaad..974467a8d20d0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -463,9 +463,9 @@ "common.ui.management.breadcrumb": "管理", "common.ui.management.connectDataDisplayName": "连接数据", "common.ui.management.displayName": "管理", - "common.ui.management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", - "common.ui.management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", - "common.ui.management.editIndexPattern.createIndex.defaultTypeName": "索引模式", + "management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", + "management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", + "management.editIndexPattern.createIndex.defaultTypeName": "索引模式", "common.ui.management.nav.menu": "管理菜单", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", @@ -12136,23 +12136,7 @@ "visTypeMarkdown.params.fontSizeLabel": "基础字体大小(磅)", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "未注册操作类型“{id}”。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "操作类型“{id}”已注册。", - "xpack.actions.builtin.email.errorSendingErrorMessage": "发送电子邮件时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "索引数据时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.esIndex.indexParamRequiredErrorMessage": "需要设置索引参数,因为在操作 {actionId} 的配置中未设置", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:状态 {status},请稍后重试", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "发布事件时 pagerduty 操作“{actionId}”出现错误:异常错误 {status}", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "记录消息时操作“{actionId}”出现错误:{errorMessage}", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "发布 slack 消息时操作“{id}”出现错误:{message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时操作“{id}”出现错误,请稍后重试", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "来自 slack 的异常空响应", - "xpack.actions.builtin.slack.unexpectedTextResponseErrorMessage": "来自 slack 的异常文本响应", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请在 {retryString} 重试:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "无效响应:调用远程 Webhook 时 Webhook 操作“{id}”发生错误,请稍后重试", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "Webhook无法访问:调用远程 Webhook 时 Webhook 操作“{id}”发生错误:{message}", - "xpack.actions.builtin.webhook.unreachableRemoteWebhook": "远程 Webhook 无法访问,是否确定地址正确?", "xpack.actions.builtin.webhook.webhookConfigurationError": "配置 Webhook 操作时出错:{message}", "xpack.actions.urlWhitelistConfigurationError": "目标 {field}“{value}”不在 Kibana 白名单中", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "添加到面板", diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index b2e69a2121825..30b235a784c22 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -19,7 +19,7 @@ export default function(kibana: any) { id: 'test.noop', name: 'Test: Noop', async executor() { - return { status: 'ok' }; + return { status: 'ok', actionId: '' }; }, }; const indexRecordActionType: ActionType = { @@ -101,6 +101,7 @@ export default function(kibana: any) { return { status: 'error', retry: new Date(params.retryAt), + actionId: '', }; }, }; @@ -162,6 +163,7 @@ export default function(kibana: any) { }); return { status: 'ok', + actionId: '', }; }, }; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index 197ad3ff80094..aacf0b8f87ed0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -234,7 +234,7 @@ export default function indexTest({ getService }: FtrProviderContext) { result = response.body; expect(result.status).to.equal('error'); expect(result.message).to.eql( - `index param needs to be set because not set in config for action ${createdActionID}` + 'index param needs to be set because not set in config for action' ); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index 62c8b31209729..54f38d2fd747e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -121,6 +121,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result).to.eql({ status: 'ok', + actionId: simulatedActionId, data: { dedup_key: `action:${simulatedActionId}`, message: 'Event processed', @@ -140,9 +141,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: unexpected status 418/ - ); + expect(result.message).to.match(/error posting pagerduty event: unexpected status 418/); }); it('should handle a 429 pagerduty error', async () => { @@ -158,7 +157,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(result.status).to.equal('error'); expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 429, retry later/ + /error posting pagerduty event: http status 429, retry later/ ); expect(result.retry).to.equal(true); }); @@ -175,9 +174,7 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match( - /error in pagerduty action .+ posting event: status 502, retry later/ - ); + expect(result.message).to.match(/error posting pagerduty event: http status 502/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index ae1464023c011..288eda5111a1e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -126,7 +126,7 @@ export default function slackTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/unexpected http response from slack: /); }); it('should handle a 429 slack error', async () => { @@ -142,8 +142,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); - expect(result.message).to.match(/retry at/); + expect(result.message).to.match(/error posting a slack message, retry at \d\d\d\d-/); const dateRetry = new Date(result.retry).getTime(); expect(dateRetry).to.greaterThan(dateStart); @@ -161,7 +160,7 @@ export default function slackTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.equal('error'); - expect(result.message).to.match(/an error occurred in action .+ posting a slack message/); + expect(result.message).to.match(/error posting a slack message, retry later/); expect(result.retry).to.equal(true); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 53b11525d1b95..426bbe853ca19 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -196,7 +196,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Unreachable Remote Webhook/); + expect(result.message).to.match(/error calling webhook, unexpected error/); }); it('should handle failing webhook targets', async () => { const webhookActionId = await createWebhookAction(webhookSimulatorURL); @@ -211,7 +211,8 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/Bad Request/); + expect(result.message).to.match(/error calling webhook, invalid response/); + expect(result.serviceMessage).to.eql('[400] Bad Request'); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index edfb340bfd784..8f3996f958bb2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -532,6 +532,7 @@ export default function alertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'superuser at space1': + expect(response.statusCode).to.eql(200); // Wait for actions to execute twice before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.index-record', reference, 2); await alertUtils.disable(response.body.id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index bf61ee2e3f137..c0a99ae068e71 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -31,11 +31,32 @@ export default function createAlertTests({ getService }: FtrProviderContext) { const { user, space } = scenario; describe(scenario.id, () => { it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -56,7 +77,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 31af7a0acffbb..359058f2ac23a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -78,10 +78,32 @@ export default function createFindTests({ getService }: FtrProviderContext) { }); it('should handle find alert request with filter appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()) + .send( + getTestAlertData({ + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ) .expect(200); objectRemover.add(space.id, createdAlert.id, 'alert'); @@ -89,7 +111,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { .get( `${getUrlPrefix( space.id - )}/api/alert/_find?filter=alert.attributes.alertTypeId:test.noop` + )}/api/alert/_find?filter=alert.attributes.actions:{ actionTypeId: test.noop }` ) .auth(user.username, user.password); @@ -117,11 +139,17 @@ export default function createFindTests({ getService }: FtrProviderContext) { tags: ['foo'], alertTypeId: 'test.noop', interval: '1m', - enabled: true, - actions: [], + enabled: false, + actions: [ + { + id: createdAction.id, + group: 'default', + actionTypeId: 'test.noop', + params: {}, + }, + ], params: {}, createdBy: 'elastic', - scheduledTaskId: match.scheduledTaskId, throttle: '1m', updatedBy: 'elastic', apiKeyOwner: 'elastic', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 3018f8efffffe..929905a958abb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -27,10 +27,31 @@ export default function createAlertTests({ getService }: FtrProviderContext) { } it('should handle create alert request appropriately', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + actionTypeId: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + const response = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) .set('kbn-xsrf', 'foo') - .send(getTestAlertData()); + .send( + getTestAlertData({ + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + }) + ); expect(response.statusCode).to.eql(200); objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); @@ -38,7 +59,14 @@ export default function createAlertTests({ getService }: FtrProviderContext) { id: response.body.id, name: 'abc', tags: ['foo'], - actions: [], + actions: [ + { + id: createdAction.id, + actionTypeId: createdAction.actionTypeId, + group: 'default', + params: {}, + }, + ], enabled: true, alertTypeId: 'test.noop', params: {}, diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index 58ee1668df701..2bbc39969370b 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,8 +13,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/45321 - describe.skip('graph', function() { + describe('graph', function() { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data'); @@ -114,6 +113,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const circlesText = nodes.map(({ label }) => label); expect(circlesText.length).to.equal(expectedNodes.length); circlesText.forEach(circleText => { + log.debug(`Looking for ${circleText}`); expect(expectedNodes.includes(circleText)).to.be(true); }); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index f4b2be3420298..bf3435582ef47 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -153,10 +153,10 @@ export default async function ({ readConfigFile }) { pathname: '/app/uptime', }, apm: { - pathname: '/app/apm' + pathname: '/app/apm', }, ml: { - pathname: '/app/ml' + pathname: '/app/ml', }, rollupJob: { pathname: '/app/kibana', diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index 78863f91451f6..a4b4f500b8832 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -11,8 +11,22 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function CanvasPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); + const browser = getService('browser'); return { + async enterFullscreen() { + const elem = await find.byCssSelector('[aria-label="View fullscreen"]', 20000); + await elem.click(); + }, + + async exitFullscreen() { + await browser.pressKeys(browser.keys.ESCAPE); + }, + + async waitForWorkpadElements() { + await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); + }, + async expectCreateWorkpadButtonEnabled() { const button = await testSubjects.find('create-workpad-button', 20000); const disabledAttr = await button.getAttribute('disabled'); diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 780a541db0890..0d3e2c10579f5 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -235,6 +235,11 @@ export function GraphPageProvider({ getService, getPageObjects }: FtrProviderCon await this.goToListingPage(); await this.searchForWorkspaceWithName(name); await find.clickByLinkText(name); + // wait for nodes to show up + if (!(await find.existsByCssSelector('.gphNode', 10000))) { + throw new Error('nodes did not show up'); + } + // let force simulation settle down before continuing await PageObjects.common.sleep(5000); } diff --git a/x-pack/test/functional/page_objects/upgrade_assistant.js b/x-pack/test/functional/page_objects/upgrade_assistant.js index c6977b2150840..fc6faed170c9f 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant.js +++ b/x-pack/test/functional/page_objects/upgrade_assistant.js @@ -45,7 +45,7 @@ export function UpgradeAssistantProvider({ getService, getPageObjects }) { async expectDeprecationLoggingLabel(labelText) { return await retry.try(async () => { log.debug('expectDeprecationLoggingLabel()'); - const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ p'); + const label = await find.byCssSelector('[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span'); const value = await label.getVisibleText(); expect(value).to.equal(labelText); }); diff --git a/x-pack/test/functional/services/machine_learning/common.ts b/x-pack/test/functional/services/machine_learning/common.ts new file mode 100644 index 0000000000000..12b9e8a1cfb29 --- /dev/null +++ b/x-pack/test/functional/services/machine_learning/common.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +interface SetValueOptions { + clearWithKeyboard?: boolean; + typeCharByChar?: boolean; +} + +export function MachineLearningCommonProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + + return { + async setValueWithChecks( + selector: string, + text: string, + options: SetValueOptions = {} + ): Promise { + return await retry.try(async () => { + const { clearWithKeyboard = false, typeCharByChar = false } = options; + log.debug(`TestSubjects.setValueWithChecks(${selector}, ${text})`); + await testSubjects.click(selector); + // in case the input element is actually a child of the testSubject, we + // call clearValue() and type() on the element that is focused after + // clicking on the testSubject + const input = await find.activeElement(); + + await retry.tryForTime(5000, async () => { + let currentValue = await input.getAttribute('value'); + if (currentValue !== '') { + if (clearWithKeyboard === true) { + await input.clearValueWithKeyboard(); + } else { + await input.clearValue(); + } + currentValue = await input.getAttribute('value'); + } + + if (currentValue === '') { + return true; + } else { + throw new Error(`Expected input to be empty, but got value '${currentValue}'`); + } + }); + + for (const chr of text) { + await retry.tryForTime(5000, async () => { + const oldValue = await input.getAttribute('value'); + await input.type(chr, { charByChar: typeCharByChar }); + + await retry.tryForTime(1000, async () => { + const newValue = await input.getAttribute('value'); + if (newValue === `${oldValue}${chr}`) { + return true; + } else { + throw new Error( + `After typing character '${chr}', the new value in the input should be '${oldValue}${chr}' (got ${newValue})` + ); + } + }); + }); + } + }); + }, + }; +} diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts index adaded0832522..7c393689d166c 100644 --- a/x-pack/test/functional/services/machine_learning/index.ts +++ b/x-pack/test/functional/services/machine_learning/index.ts @@ -6,6 +6,7 @@ export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer'; export { MachineLearningAPIProvider } from './api'; +export { MachineLearningCommonProvider } from './common'; export { MachineLearningCustomUrlsProvider } from './custom_urls'; export { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytics'; export { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation'; diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 27768f784b1c5..ab53b0412ca35 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCommonProvider } from './common'; -export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProviderContext) { +export function MachineLearningJobWizardAdvancedProvider( + { getService }: FtrProviderContext, + mlCommon: ProvidedType +) { const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); @@ -44,7 +49,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setQueryDelay(queryDelay: string) { - await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, { + await mlCommon.setValueWithChecks('mlJobWizardInputQueryDelay', queryDelay, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -61,7 +66,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setFrequency(frequency: string) { - await testSubjects.setValue('mlJobWizardInputFrequency', frequency, { + await mlCommon.setValueWithChecks('mlJobWizardInputFrequency', frequency, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -78,7 +83,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setScrollSize(scrollSize: string) { - await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, { + await mlCommon.setValueWithChecks('mlJobWizardInputScrollSize', scrollSize, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -257,7 +262,7 @@ export function MachineLearningJobWizardAdvancedProvider({ getService }: FtrProv }, async setDetectorDescription(description: string) { - await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, { + await mlCommon.setValueWithChecks('mlAdvancedDetectorDescriptionInput', description, { clearWithKeyboard: true, }); await this.assertDetectorDescriptionValue(description); diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 235e597f8c280..b9e6822c8f41a 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -7,10 +7,12 @@ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test/types/ftr'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { MachineLearningCommonProvider } from './common'; import { MachineLearningCustomUrlsProvider } from './custom_urls'; export function MachineLearningJobWizardCommonProvider( { getService }: FtrProviderContext, + mlCommon: ProvidedType, customUrls: ProvidedType ) { const comboBox = getService('comboBox'); @@ -113,7 +115,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setBucketSpan(bucketSpan: string) { - await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan, { + await mlCommon.setValueWithChecks('mlJobWizardInputBucketSpan', bucketSpan, { clearWithKeyboard: true, typeCharByChar: true, }); @@ -130,7 +132,9 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobId(jobId: string) { - await testSubjects.setValue('mlJobWizardInputJobId', jobId, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks('mlJobWizardInputJobId', jobId, { + clearWithKeyboard: true, + }); await this.assertJobIdValue(jobId); }, @@ -146,7 +150,7 @@ export function MachineLearningJobWizardCommonProvider( }, async setJobDescription(jobDescription: string) { - await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription, { + await mlCommon.setValueWithChecks('mlJobWizardInputJobDescription', jobDescription, { clearWithKeyboard: true, }); await this.assertJobDescriptionValue(jobDescription); @@ -307,7 +311,7 @@ export function MachineLearningJobWizardCommonProvider( await this.ensureAdvancedSectionOpen(); subj = advancedSectionSelector(subj); } - await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); await this.assertModelMemoryLimitValue(modelMemoryLimit, { withAdvancedSection: sectionOptions.withAdvancedSection, }); diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts index 4b6f77262b7f9..8e71c16921078 100644 --- a/x-pack/test/functional/services/ml.ts +++ b/x-pack/test/functional/services/ml.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; import { MachineLearningAnomalyExplorerProvider, MachineLearningAPIProvider, + MachineLearningCommonProvider, MachineLearningCustomUrlsProvider, MachineLearningDataFrameAnalyticsProvider, MachineLearningDataFrameAnalyticsCreationProvider, @@ -29,6 +30,8 @@ import { } from './machine_learning'; export function MachineLearningProvider(context: FtrProviderContext) { + const common = MachineLearningCommonProvider(context); + const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context); const api = MachineLearningAPIProvider(context); const customUrls = MachineLearningCustomUrlsProvider(context); @@ -41,8 +44,8 @@ export function MachineLearningProvider(context: FtrProviderContext) { const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); const jobTable = MachineLearningJobTableProvider(context); const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context); - const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context); - const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, customUrls); + const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, common); + const jobWizardCommon = MachineLearningJobWizardCommonProvider(context, common, customUrls); const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context); const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context); const navigation = MachineLearningNavigationProvider(context); diff --git a/x-pack/test/visual_regression/config.js b/x-pack/test/visual_regression/config.js index 4f77098d6a456..ad9a714ee8d02 100644 --- a/x-pack/test/visual_regression/config.js +++ b/x-pack/test/visual_regression/config.js @@ -13,6 +13,7 @@ export default async function ({ readConfigFile }) { ...functionalConfig.getAll(), testFiles: [ + require.resolve('./tests/canvas'), require.resolve('./tests/login_page'), require.resolve('./tests/maps'), require.resolve('./tests/infra'), diff --git a/x-pack/test/visual_regression/tests/canvas/fullscreen.js b/x-pack/test/visual_regression/tests/canvas/fullscreen.js new file mode 100644 index 0000000000000..998fbb2c21f40 --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/fullscreen.js @@ -0,0 +1,24 @@ +/* +* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +* or more contributor license agreements. Licensed under the Elastic License; +* you may not use this file except in compliance with the Elastic License. +*/ + +export default function ({ getPageObjects, getService }) { + const PageObjects = getPageObjects(['common', 'canvas']); + const visualTesting = getService('visualTesting'); + + describe('fullscreen', () => { + it('workpad should display properly in fullscreen mode', async () => { + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1' + }); + + await PageObjects.canvas.enterFullscreen(); + + await PageObjects.canvas.waitForWorkpadElements(); + + await visualTesting.snapshot(); + }); + }); +} diff --git a/x-pack/test/visual_regression/tests/canvas/index.js b/x-pack/test/visual_regression/tests/canvas/index.js new file mode 100644 index 0000000000000..29bfd07c525cd --- /dev/null +++ b/x-pack/test/visual_regression/tests/canvas/index.js @@ -0,0 +1,29 @@ +/* +* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +* or more contributor license agreements. Licensed under the Elastic License; +* you may not use this file except in compliance with the Elastic License. +*/ +import { DEFAULT_OPTIONS } from '../../../../../test/visual_regression/services/visual_testing/visual_testing'; + +const [SCREEN_WIDTH] = DEFAULT_OPTIONS.widths || []; + +export default function ({ loadTestFile, getService }) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('canvas app visual regression', function () { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('canvas/default'); + + await browser.setWindowSize(SCREEN_WIDTH, 1000); + }); + + after(async () => { + await esArchiver.unload('canvas/default'); + }); + + this.tags('ciGroup10'); + loadTestFile(require.resolve('./fullscreen')); + }); +} diff --git a/yarn.lock b/yarn.lock index 57249563f4566..cfef1bec7e6a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,10 +1226,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@16.1.0": - version "16.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-16.1.0.tgz#b8311a4dd3146da2fcd8cec6ae1ed46e26f90746" - integrity sha512-WbfIc2RGojrUeYyEIjiQ0Cy4xbk6YJ6eQ+ymFHSDGqwkKy5IidSBZIDNLM13LzAhxBFTAMmfVsk4lB2qiFlolQ== +"@elastic/eui@17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-17.0.0.tgz#2b53500b9f4849fdf58f88ea89e10a0b58836acc" + integrity sha512-bWTVKbChPGh7AHLxW1o9yp7O3hL9lTVj69DVaaLBOz9K0JnDrWZ5VDvWL9AAguFWq9OEMY9oWjyBhQZr1ZeZbA== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -4238,24 +4238,24 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.9.0.tgz#fa810282c0e45f6c2310b9c0dfcd25bff97ab7e9" - integrity sha512-98rfOt3NYn5Gr9wekTB8TexxN6oM8ZRvYuphPs1Atfsy419SDLYCaE30aJkRiiTCwGEY98vOhFsEVm7Zs4toQQ== +"@typescript-eslint/eslint-plugin@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.10.0.tgz#c4cb103275e555e8a7e9b3d14c5951eb6d431e70" + integrity sha512-rT51fNLW0u3fnDGnAHVC5nu+Das+y2CpW10yqvf6/j5xbuUV3FxA3mBaIbM24CXODXjbgUznNb4Kg9XZOUxKAw== dependencies: - "@typescript-eslint/experimental-utils" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.9.0.tgz#bbe99a8d9510240c055fc4b17230dd0192ba3c7f" - integrity sha512-0lOLFdpdJsCMqMSZT7l7W2ta0+GX8A3iefG3FovJjrX+QR8y6htFlFdU7aOVPL6pDvt6XcsOb8fxk5sq+girTw== +"@typescript-eslint/experimental-utils@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.10.0.tgz#8db1656cdfd3d9dcbdbf360b8274dea76f0b2c2c" + integrity sha512-FZhWq6hWWZBP76aZ7bkrfzTMP31CCefVIImrwP3giPLcoXocmLTmr92NLZxuIcTL4GTEOE33jQMWy9PwelL+yQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-scope "^5.0.0" "@typescript-eslint/experimental-utils@^1.13.0": @@ -4267,14 +4267,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/parser@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.9.0.tgz#2e9cf438de119b143f642a3a406e1e27eb70b7cd" - integrity sha512-fJ+dNs3CCvEsJK2/Vg5c2ZjuQ860ySOAsodDPwBaVlrGvRN+iCNC8kUfLFL8cT49W4GSiLPa/bHiMjYXA7EhKQ== +"@typescript-eslint/parser@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.10.0.tgz#24b2e48384ab6d5a6121e4c4faf8892c79657ad3" + integrity sha512-wQNiBokcP5ZsTuB+i4BlmVWq6o+oAhd8en2eSm/EE9m7BgZUIfEeYFd6z3S+T7bgNuloeiHA1/cevvbBDLr98g== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.9.0" - "@typescript-eslint/typescript-estree" "2.9.0" + "@typescript-eslint/experimental-utils" "2.10.0" + "@typescript-eslint/typescript-estree" "2.10.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -4285,10 +4285,10 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.9.0.tgz#09138daf8f47d0e494ba7db9e77394e928803017" - integrity sha512-v6btSPXEWCP594eZbM+JCXuFoXWXyF/z8kaSBSdCb83DF+Y7+xItW29SsKtSULgLemqJBT+LpT+0ZqdfH7QVmA== +"@typescript-eslint/typescript-estree@2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.10.0.tgz#89cdabd5e8c774e9d590588cb42fb9afd14dcbd9" + integrity sha512-oOYnplddQNm/LGVkqbkAwx4TIBuuZ36cAQq9v3nFIU9FmhemHuVzAesMSXNQDdAzCa5bFgCrfD3JWhYVKlRN2g== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0"