diff --git a/.buildkite/pipelines/pull_request/osquery_cypress.yml b/.buildkite/pipelines/pull_request/osquery_cypress.yml new file mode 100644 index 000000000000..766d28e0877c --- /dev/null +++ b/.buildkite/pipelines/pull_request/osquery_cypress.yml @@ -0,0 +1,11 @@ +steps: + - command: .buildkite/scripts/steps/functional/osquery_cypress.sh + label: 'Osquery Cypress Tests' + agents: + queue: ci-group-6 + depends_on: build + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 1 diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index d0f38dc77335..ab125d4f7337 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -86,6 +86,16 @@ const uploadPipeline = (pipelineContent) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fleet_cypress.yml')); } + if ( + (await doAnyChangesMatch([ + /^x-pack\/plugins\/osquery/, + /^x-pack\/test\/osquery_cypress/, + ])) || + process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/osquery_cypress.yml')); + } + if (await doAnyChangesMatch([/^x-pack\/plugins\/uptime/])) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/uptime.yml')); } diff --git a/.buildkite/scripts/steps/functional/osquery_cypress.sh b/.buildkite/scripts/steps/functional/osquery_cypress.sh new file mode 100755 index 000000000000..a23d41c4f8d4 --- /dev/null +++ b/.buildkite/scripts/steps/functional/osquery_cypress.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh +.buildkite/scripts/download_build_artifacts.sh + +export JOB=kibana-osquery-cypress + +echo "--- Osquery Cypress tests" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Osquery Cypress Tests" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --config test/osquery_cypress/cli_config.ts diff --git a/dev_docs/key_concepts/performance.mdx b/dev_docs/key_concepts/performance.mdx index 0201c7774f85..5d955c789dde 100644 --- a/dev_docs/key_concepts/performance.mdx +++ b/dev_docs/key_concepts/performance.mdx @@ -3,11 +3,13 @@ id: kibDevPerformance slug: /kibana-dev-docs/key-concepts/performance title: Performance summary: Performance tips for Kibana development. -date: 2021-09-02 +date: 2021-12-03 tags: ['kibana', 'onboarding', 'dev', 'performance'] --- -## Keep Kibana fast +## Client-side considerations + +### Lazy load code _tl;dr_: Load as much code lazily as possible. Everyone loves snappy applications with a responsive UI and hates spinners. Users deserve the @@ -105,3 +107,15 @@ Many OSS tools allow you to analyze the generated stats file: Webpack authors - [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/) - [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) + +## Server-side considerations + +### Don't block the event loop + +[Node.js is single threaded](https://nodejs.dev/learn/introduction-to-nodejs) which means a single CPU-intensive server-side, synchronous operation will block any other functionality waiting to execute on the Kibana server. The affects background tasks, like alerts, and search sessions, as well as search requests and page loads. + +**When writing code that will run on the server, [don't block the event loop](https://nodejs.org/en/docs/guides/dont-block-the-event-loop/)**. Instead consider: + +- Writing async code. For example, leverage [setImmediate](https://nodejs.dev/learn/understanding-setimmediate) inside for loops. +- Executing logic on the client instead. This may not be a good option if you require a lot of data going back and forth between the server and the client, as that can also slow down the user's experience, especially over slower bandwidth internet connections. +- Worker threads are also an option if the code doesn't rely on stateful Kibana services. If you are interested in using worker threads, please reach out to a tech-lead before doing so. We will likely want to implement a worker threads pool to ensure worker threads cooperate appropriately. \ No newline at end of file diff --git a/dev_docs/tutorials/saved_objects.mdx b/dev_docs/tutorials/saved_objects.mdx index 9583e195d1c8..a9d8cd7c6ec1 100644 --- a/dev_docs/tutorials/saved_objects.mdx +++ b/dev_docs/tutorials/saved_objects.mdx @@ -252,6 +252,8 @@ Having said that, if a document is encountered that is not in the expected shape fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time. When such a scenario is encountered, the error should be verbose and informative so that the corrupt document can be corrected, if possible. +**WARNING:** Do not attempt to change the `migrationVersion`, `id`, or `type` fields within a migration function, this is not supported. + ### Testing Migrations Bugs in a migration function cause downtime for our users and therefore have a very high impact. Follow the . diff --git a/docs/developer/architecture/core/saved-objects-service.asciidoc b/docs/developer/architecture/core/saved-objects-service.asciidoc index a7ce86ea4635..54a5c319c622 100644 --- a/docs/developer/architecture/core/saved-objects-service.asciidoc +++ b/docs/developer/architecture/core/saved-objects-service.asciidoc @@ -259,6 +259,9 @@ upgrade. In most scenarios, it is better to fail an upgrade than to silently ignore a corrupt document which can cause unexpected behaviour at some future point in time. +WARNING: Do not attempt to change the `migrationVersion`, `id`, or `type` fields +within a migration function, this is not supported. + It is critical that you have extensive tests to ensure that migrations behave as expected with all possible input documents. Given how simple it is to test all the branch conditions in a migration function and the high impact of a bug diff --git a/package.json b/package.json index 1e5a135b723e..374ccee71ec6 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "**/hoist-non-react-statics": "^3.3.2", "**/html-minifier/uglify-js": "^3.14.3", "**/isomorphic-fetch/node-fetch": "^2.6.1", - "**/istanbul-instrumenter-loader/schema-utils": "1.0.0", + "**/istanbul-lib-coverage": "^3.2.0", "**/json-schema": "^0.4.0", "**/minimist": "^1.2.5", "**/node-jose/node-forge": "^0.10.0", @@ -436,6 +436,7 @@ "@babel/types": "^7.16.0", "@bazel/ibazel": "^0.15.10", "@bazel/typescript": "^3.8.0", + "@cypress/code-coverage": "^3.9.11", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.6.0", "@elastic/eslint-config-kibana": "link:bazel-bin/packages/elastic-eslint-config-kibana", @@ -565,6 +566,8 @@ "@types/kbn__apm-utils": "link:bazel-bin/packages/kbn-apm-utils/npm_module_types", "@types/kbn__cli-dev-mode": "link:bazel-bin/packages/kbn-cli-dev-mode/npm_module_types", "@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types", + "@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types", + "@types/kbn__crypto": "link:bazel-bin/packages/kbn-crypto/npm_module_types", "@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types", "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", "@types/license-checker": "15.0.0", @@ -694,7 +697,9 @@ "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.5.0", "cypress-pipe": "^2.0.0", + "cypress-react-selector": "^2.3.13", "cypress-real-events": "^1.5.1", + "cypress-recurse": "^1.13.1", "debug": "^2.6.9", "delete-empty": "^2.0.0", "dependency-check": "^4.1.0", @@ -749,7 +754,6 @@ "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-path-inside": "^3.0.2", - "istanbul-instrumenter-loader": "^3.0.1", "jest": "^26.6.3", "jest-canvas-mock": "^2.3.1", "jest-circus": "^26.6.3", @@ -786,7 +790,7 @@ "ncp": "^2.0.0", "node-sass": "^6.0.1", "null-loader": "^3.0.0", - "nyc": "^15.0.1", + "nyc": "^15.1.0", "oboe": "^2.1.4", "parse-link-header": "^1.0.1", "pbf": "3.2.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 8208496f7d80..96b184614768 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -84,6 +84,8 @@ filegroup( "//packages/kbn-apm-utils:build_types", "//packages/kbn-cli-dev-mode:build_types", "//packages/kbn-config:build_types", + "//packages/kbn-config-schema:build_types", + "//packages/kbn-crypto:build_types", "//packages/kbn-i18n:build_types", "//packages/kbn-i18n-react:build_types", ], diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel index c6611e71e35a..66e00706e9e5 100644 --- a/packages/kbn-cli-dev-mode/BUILD.bazel +++ b/packages/kbn-cli-dev-mode/BUILD.bazel @@ -49,7 +49,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-dev-utils", "//packages/kbn-logging", "//packages/kbn-optimizer", diff --git a/packages/kbn-config-schema/BUILD.bazel b/packages/kbn-config-schema/BUILD.bazel index 70de78b7617c..ed6082527bab 100644 --- a/packages/kbn-config-schema/BUILD.bazel +++ b/packages/kbn-config-schema/BUILD.bazel @@ -1,9 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-config-schema" PKG_REQUIRE_NAME = "@kbn/config-schema" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__config-schema" SOURCE_FILES = glob([ "src/**/*.ts", @@ -72,7 +73,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -91,3 +92,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index a3b6880c3293..d6a0e91b9b42 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -1,7 +1,6 @@ { "name": "@kbn/config-schema", "main": "./target_node/index.js", - "types": "./target_types/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", "private": true diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 8635421beb0a..f9db84f255ec 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -49,7 +49,7 @@ import { StreamType, } from './types'; -export type { TypeOf, Props, NullableProps }; +export type { AnyType, ConditionalType, TypeOf, Props, NullableProps }; export { ObjectType, Type }; export { ByteSizeValue } from './byte_size_value'; export { SchemaTypeError, ValidationError } from './errors'; diff --git a/packages/kbn-config/BUILD.bazel b/packages/kbn-config/BUILD.bazel index c0b75ab491ac..0353b2d16be7 100644 --- a/packages/kbn-config/BUILD.bazel +++ b/packages/kbn-config/BUILD.bazel @@ -46,7 +46,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/elastic-safer-lodash-set", - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-logging", "//packages/kbn-std", "//packages/kbn-utility-types", diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 0f35aab46107..81ee6d770103 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -1,10 +1,11 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-crypto" PKG_REQUIRE_NAME = "@kbn/crypto" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__crypto" SOURCE_FILES = glob( [ @@ -72,7 +73,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -91,3 +92,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-crypto/package.json b/packages/kbn-crypto/package.json index 8fa6cd3c232f..96bf21906ed4 100644 --- a/packages/kbn-crypto/package.json +++ b/packages/kbn-crypto/package.json @@ -3,6 +3,5 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target_node/index.js", - "types": "./target_types/index.d.ts" + "main": "./target_node/index.js" } diff --git a/packages/kbn-docs-utils/src/api_docs/README.md b/packages/kbn-docs-utils/src/api_docs/README.md index f980fe83b959..728cb0690834 100644 --- a/packages/kbn-docs-utils/src/api_docs/README.md +++ b/packages/kbn-docs-utils/src/api_docs/README.md @@ -1,6 +1,6 @@ # Autogenerated API documentation -[RFC](../../../rfcs/text/0014_api_documentation.md) +[RFC](https://github.com/elastic/kibana/blob/main/legacy_rfcs/text/0014_api_documentation.md)) This is an experimental api documentation system that is managed by the Kibana Tech Leads until we determine the value of such a system and what kind of maintenance burder it will incur. diff --git a/packages/kbn-es-query/BUILD.bazel b/packages/kbn-es-query/BUILD.bazel index 26d2030d1b0b..70d8d659c99f 100644 --- a/packages/kbn-es-query/BUILD.bazel +++ b/packages/kbn-es-query/BUILD.bazel @@ -32,7 +32,6 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "//packages/kbn-utility-types", - "//packages/kbn-config-schema", "//packages/kbn-i18n", "@npm//@elastic/elasticsearch", "@npm//load-json-file", diff --git a/packages/kbn-io-ts-utils/BUILD.bazel b/packages/kbn-io-ts-utils/BUILD.bazel index e5f1de4d07f6..dd81e8318e9d 100644 --- a/packages/kbn-io-ts-utils/BUILD.bazel +++ b/packages/kbn-io-ts-utils/BUILD.bazel @@ -45,7 +45,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "@npm//fp-ts", "@npm//io-ts", "@npm//tslib", diff --git a/packages/kbn-optimizer/BUILD.bazel b/packages/kbn-optimizer/BUILD.bazel index cc03c8107074..a389086c9ee3 100644 --- a/packages/kbn-optimizer/BUILD.bazel +++ b/packages/kbn-optimizer/BUILD.bazel @@ -63,7 +63,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-dev-utils", "//packages/kbn-std", "//packages/kbn-ui-shared-deps-npm", diff --git a/packages/kbn-server-http-tools/BUILD.bazel b/packages/kbn-server-http-tools/BUILD.bazel index 609fe6d00f17..be74c363a7ac 100644 --- a/packages/kbn-server-http-tools/BUILD.bazel +++ b/packages/kbn-server-http-tools/BUILD.bazel @@ -37,8 +37,8 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-config-schema", - "//packages/kbn-crypto", + "//packages/kbn-config-schema:npm_module_types", + "//packages/kbn-crypto:npm_module_types", "@npm//@hapi/hapi", "@npm//@hapi/hoek", "@npm//joi", diff --git a/packages/kbn-server-route-repository/BUILD.bazel b/packages/kbn-server-route-repository/BUILD.bazel index 9f8a9f34061d..6e7e10d4dd81 100644 --- a/packages/kbn-server-route-repository/BUILD.bazel +++ b/packages/kbn-server-route-repository/BUILD.bazel @@ -36,7 +36,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-io-ts-utils", "@npm//@hapi/boom", "@npm//fp-ts", diff --git a/packages/kbn-utils/BUILD.bazel b/packages/kbn-utils/BUILD.bazel index c2f82d65d331..c4d256e7672a 100644 --- a/packages/kbn-utils/BUILD.bazel +++ b/packages/kbn-utils/BUILD.bazel @@ -31,7 +31,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-config-schema", + "//packages/kbn-config-schema:npm_module_types", "@npm//load-json-file", "@npm//tslib", "@npm//@types/jest", diff --git a/packages/kbn-utils/src/path/index.test.ts b/packages/kbn-utils/src/path/index.test.ts index 307d47af9ac5..e4c80a0783b5 100644 --- a/packages/kbn-utils/src/path/index.test.ts +++ b/packages/kbn-utils/src/path/index.test.ts @@ -7,10 +7,17 @@ */ import { accessSync, constants } from 'fs'; -import { createAbsolutePathSerializer } from '@kbn/dev-utils'; import { getConfigPath, getDataPath, getLogsPath, getConfigDirectory } from './'; - -expect.addSnapshotSerializer(createAbsolutePathSerializer()); +import { REPO_ROOT } from '../repo_root'; + +expect.addSnapshotSerializer( + ((rootPath: string = REPO_ROOT, replacement = '') => { + return { + test: (value: any) => typeof value === 'string' && value.startsWith(rootPath), + serialize: (value: string) => value.replace(rootPath, replacement).replace(/\\/g, '/'), + }; + })() +); describe('Default path finder', () => { it('should expose a path to the config directory', () => { diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 64c1c4ce2fa9..f92d505c058e 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -664,39 +664,6 @@ describe('DocumentMigrator', () => { ); }); - it('allows updating a migrationVersion prop to a later version', () => { - const migrator = new DocumentMigrator({ - ...testOpts(), - typeRegistry: createRegistry({ - name: 'cat', - migrations: { - '1.0.0': setAttr('migrationVersion.cat', '2.9.1'), - '2.0.0': () => { - throw new Error('POW!'); - }, - '2.9.1': () => { - throw new Error('BANG!'); - }, - '3.0.0': setAttr('attributes.name', 'Shiny'), - }, - }), - }); - migrator.prepareMigrations(); - const actual = migrator.migrate({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Boo' }, - migrationVersion: { cat: '0.5.6' }, - }); - expect(actual).toEqual({ - id: 'smelly', - type: 'cat', - attributes: { name: 'Shiny' }, - migrationVersion: { cat: '3.0.0' }, - coreMigrationVersion: kibanaVersion, - }); - }); - it('allows adding props to migrationVersion', () => { const migrator = new DocumentMigrator({ ...testOpts(), @@ -1072,7 +1039,8 @@ describe('DocumentMigrator', () => { name: 'dog', namespaceType: 'single', migrations: { - '1.0.0': setAttr('migrationVersion.dog', '2.0.0'), + '1.1.0': setAttr('attributes.age', '12'), + '1.5.0': setAttr('attributes.color', 'tri-color'), '2.0.0': (doc) => doc, // noop }, }, @@ -1083,9 +1051,10 @@ describe('DocumentMigrator', () => { const obj = { id: 'sleepy', type: 'dog', - attributes: { name: 'Patches' }, - migrationVersion: {}, + attributes: { name: 'Patches', age: '11' }, + migrationVersion: { dog: '1.1.0' }, // skip the first migration transform, only apply the second and third references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], + coreMigrationVersion: undefined, // this is intentional }; it('in the default space', () => { @@ -1095,7 +1064,7 @@ describe('DocumentMigrator', () => { { id: 'sleepy', type: 'dog', - attributes: { name: 'Patches' }, + attributes: { name: 'Patches', age: '11', color: 'tri-color' }, migrationVersion: { dog: '2.0.0' }, references: [{ id: 'favorite', type: 'toy', name: 'BALL!' }], // no change coreMigrationVersion: kibanaVersion, @@ -1111,7 +1080,7 @@ describe('DocumentMigrator', () => { { id: 'sleepy', type: 'dog', - attributes: { name: 'Patches' }, + attributes: { name: 'Patches', age: '11', color: 'tri-color' }, migrationVersion: { dog: '2.0.0' }, references: [{ id: 'uuidv5', type: 'toy', name: 'BALL!' }], // changed coreMigrationVersion: kibanaVersion, diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index da16dbc5e69e..5f2870fb6e24 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -27,15 +27,7 @@ * handle property addition / deletion / renaming. * * A caveat is that this means we must restrict what a migration can do to the doc's - * migrationVersion itself. We allow only these kinds of changes: - * - * - Add a new property to migrationVersion - * - Move a migrationVersion property forward to a later version - * - * Migrations *cannot* move a migrationVersion property backwards (e.g. from 2.0.0 to 1.0.0), and they - * cannot clear a migrationVersion property, as allowing either of these could produce infinite loops. - * However, we do wish to allow migrations to modify migrationVersion if they wish, so that - * they could transform a type from "foo 1.0.0" to "bar 3.0.0". + * migrationVersion itself. Migrations should *not* make any changes to the migrationVersion property. * * One last gotcha is that any docs which have no migrationVersion are assumed to be up-to-date. * This is because Kibana UI and other clients really can't be expected build the migrationVersion @@ -753,12 +745,6 @@ function migrateProp( let additionalDocs: SavedObjectUnsanitizedDoc[] = []; for (const { version, transform, transformType } of applicableTransforms(migrations, doc, prop)) { - const currentVersion = propVersion(doc, prop); - if (currentVersion && Semver.gt(currentVersion, version)) { - // the previous transform function increased the object's migrationVersion; break out of the loop - break; - } - if (convertNamespaceTypes || (transformType !== 'convert' && transformType !== 'reference')) { // migrate transforms are always applied, but conversion transforms and reference transforms are only applied during index migrations const result = transform(doc); diff --git a/src/core/server/saved_objects/service/lib/repository.test.ts b/src/core/server/saved_objects/service/lib/repository.test.ts index 46a532cdefef..ab692b146e7f 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.ts +++ b/src/core/server/saved_objects/service/lib/repository.test.ts @@ -976,8 +976,9 @@ describe('SavedObjectsRepository', () => { describe('migration', () => { it(`migrates the docs and serializes the migrated docs`, async () => { migrator.migrateDocument.mockImplementation(mockMigrateDocument); - await bulkCreateSuccess([obj1, obj2]); - const docs = [obj1, obj2].map((x) => ({ ...x, ...mockTimestampFields })); + const modifiedObj1 = { ...obj1, coreMigrationVersion: '8.0.0' }; + await bulkCreateSuccess([modifiedObj1, obj2]); + const docs = [modifiedObj1, obj2].map((x) => ({ ...x, ...mockTimestampFields })); expectMigrationArgs(docs[0], true, 1); expectMigrationArgs(docs[1], true, 2); @@ -2556,8 +2557,22 @@ describe('SavedObjectsRepository', () => { it(`migrates a document and serializes the migrated doc`, async () => { const migrationVersion = mockMigrationVersion; - await createSuccess(type, attributes, { id, references, migrationVersion }); - const doc = { type, id, attributes, references, migrationVersion, ...mockTimestampFields }; + const coreMigrationVersion = '8.0.0'; + await createSuccess(type, attributes, { + id, + references, + migrationVersion, + coreMigrationVersion, + }); + const doc = { + type, + id, + attributes, + references, + migrationVersion, + coreMigrationVersion, + ...mockTimestampFields, + }; expectMigrationArgs(doc); const migratedDoc = migrator.migrateDocument(doc); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 9be58f1b7186..0d1752501604 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -305,6 +305,7 @@ export class SavedObjectsRepository { const { id = SavedObjectsUtils.generateId(), migrationVersion, + coreMigrationVersion, overwrite = false, references = [], refresh = DEFAULT_REFRESH_SETTING, @@ -359,6 +360,7 @@ export class SavedObjectsRepository { originId, attributes, migrationVersion, + coreMigrationVersion, updated_at: time, ...(Array.isArray(references) && { references }), }); @@ -523,6 +525,7 @@ export class SavedObjectsRepository { type: object.type, attributes: object.attributes, migrationVersion: object.migrationVersion, + coreMigrationVersion: object.coreMigrationVersion, ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), updated_at: time, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d4393791a74f..c599b2f71940 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -11,7 +11,7 @@ import Boom from '@hapi/boom'; import { ByteSizeValue } from '@kbn/config-schema'; import { CliArgs } from '@kbn/config'; import type { ClientOptions } from '@elastic/elasticsearch/lib/client'; -import { ConditionalType } from '@kbn/config-schema/target_types/types'; +import { ConditionalType } from '@kbn/config-schema'; import { ConfigDeprecation } from '@kbn/config'; import { ConfigDeprecationContext } from '@kbn/config'; import { ConfigDeprecationFactory } from '@kbn/config'; diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js index 0e006856dac9..866e19a1d0d3 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/worker/worker.js @@ -2022,6 +2022,10 @@ ace.define( }, // parses and returns the method method = function () { + const [first, ...rest] = text.split(' '); + text = first.toUpperCase() + rest.join(' '); + ch = ch.toUpperCase(); + switch (ch) { case 'G': next('G'); diff --git a/src/plugins/data/common/search/search_source/create_search_source.ts b/src/plugins/data/common/search/search_source/create_search_source.ts index c6c8bb4d26f9..3d2300940ac0 100644 --- a/src/plugins/data/common/search/search_source/create_search_source.ts +++ b/src/plugins/data/common/search/search_source/create_search_source.ts @@ -8,7 +8,7 @@ import { migrateLegacyQuery } from './migrate_legacy_query'; import { SearchSource, SearchSourceDependencies } from './search_source'; -import { IndexPatternsContract } from '../..'; +import { IndexPatternsContract, SerializedSearchSourceFields } from '../..'; import { SearchSourceFields } from './types'; /** @@ -28,16 +28,30 @@ import { SearchSourceFields } from './types'; * * * @public */ -export const createSearchSource = - (indexPatterns: IndexPatternsContract, searchSourceDependencies: SearchSourceDependencies) => - async (searchSourceFields: SearchSourceFields = {}) => { - const fields = { ...searchSourceFields }; +export const createSearchSource = ( + indexPatterns: IndexPatternsContract, + searchSourceDependencies: SearchSourceDependencies +) => { + const createFields = async (searchSourceFields: SerializedSearchSourceFields = {}) => { + const { index, parent, ...restOfFields } = searchSourceFields; + const fields: SearchSourceFields = { + ...restOfFields, + }; // hydrating index pattern - if (fields.index && typeof fields.index === 'string') { - fields.index = await indexPatterns.get(searchSourceFields.index as any); + if (searchSourceFields.index) { + fields.index = await indexPatterns.get(searchSourceFields.index); } + if (searchSourceFields.parent) { + fields.parent = await createFields(searchSourceFields.parent); + } + + return fields; + }; + + const createSearchSourceFn = async (searchSourceFields: SerializedSearchSourceFields = {}) => { + const fields = await createFields(searchSourceFields); const searchSource = new SearchSource(fields, searchSourceDependencies); // todo: move to migration script .. create issue @@ -49,3 +63,6 @@ export const createSearchSource = return searchSource; }; + + return createSearchSourceFn; +}; diff --git a/src/plugins/data/common/search/search_source/extract_references.ts b/src/plugins/data/common/search/search_source/extract_references.ts index dfcd1b12cb62..de32836ced12 100644 --- a/src/plugins/data/common/search/search_source/extract_references.ts +++ b/src/plugins/data/common/search/search_source/extract_references.ts @@ -8,17 +8,17 @@ import { SavedObjectReference } from 'src/core/types'; import { Filter } from '@kbn/es-query'; -import { SearchSourceFields } from './types'; +import { SerializedSearchSourceFields } from './types'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../data/common'; export const extractReferences = ( - state: SearchSourceFields -): [SearchSourceFields & { indexRefName?: string }, SavedObjectReference[]] => { - let searchSourceFields: SearchSourceFields & { indexRefName?: string } = { ...state }; + state: SerializedSearchSourceFields +): [SerializedSearchSourceFields & { indexRefName?: string }, SavedObjectReference[]] => { + let searchSourceFields: SerializedSearchSourceFields & { indexRefName?: string } = { ...state }; const references: SavedObjectReference[] = []; if (searchSourceFields.index) { - const indexId = searchSourceFields.index.id || (searchSourceFields.index as any as string); + const indexId = searchSourceFields.index; const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; references.push({ name: refName, diff --git a/src/plugins/data/common/search/search_source/inject_references.test.ts b/src/plugins/data/common/search/search_source/inject_references.test.ts index d2fd10f14b63..1785e55acc79 100644 --- a/src/plugins/data/common/search/search_source/inject_references.test.ts +++ b/src/plugins/data/common/search/search_source/inject_references.test.ts @@ -7,12 +7,12 @@ */ import { SavedObjectReference } from 'src/core/types'; -import { SearchSourceFields } from './types'; +import { SerializedSearchSourceFields } from './types'; import { injectReferences } from './inject_references'; describe('injectSearchSourceReferences', () => { - let searchSourceJSON: SearchSourceFields & { indexRefName: string }; + let searchSourceJSON: SerializedSearchSourceFields & { indexRefName: string }; let references: SavedObjectReference[]; beforeEach(() => { diff --git a/src/plugins/data/common/search/search_source/inject_references.ts b/src/plugins/data/common/search/search_source/inject_references.ts index 6729025943b9..c4b39773c440 100644 --- a/src/plugins/data/common/search/search_source/inject_references.ts +++ b/src/plugins/data/common/search/search_source/inject_references.ts @@ -7,13 +7,13 @@ */ import { SavedObjectReference } from 'src/core/types'; -import { SearchSourceFields } from './types'; +import { SerializedSearchSourceFields } from './types'; export const injectReferences = ( - searchSourceFields: SearchSourceFields & { indexRefName: string }, + searchSourceFields: SerializedSearchSourceFields & { indexRefName: string }, references: SavedObjectReference[] ) => { - const searchSourceReturnFields: SearchSourceFields = { ...searchSourceFields }; + const searchSourceReturnFields: SerializedSearchSourceFields = { ...searchSourceFields }; // Inject index id if a reference is saved if (searchSourceFields.indexRefName) { const reference = references.find((ref) => ref.name === searchSourceFields.indexRefName); diff --git a/src/plugins/data/common/search/search_source/parse_json.ts b/src/plugins/data/common/search/search_source/parse_json.ts index f34f32a0bff9..6c7d08a4f2b5 100644 --- a/src/plugins/data/common/search/search_source/parse_json.ts +++ b/src/plugins/data/common/search/search_source/parse_json.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { SearchSourceFields } from './types'; +import { SerializedSearchSourceFields } from './types'; import { InvalidJSONProperty } from '../../../../kibana_utils/common'; export const parseSearchSourceJSON = (searchSourceJSON: string) => { // if we have a searchSource, set its values based on the searchSourceJson field - let searchSourceValues: SearchSourceFields; + let searchSourceValues: SerializedSearchSourceFields; try { searchSourceValues = JSON.parse(searchSourceJSON); } catch (e) { diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 1afd2d98782a..87e249acab8b 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -944,7 +944,6 @@ describe('SearchSource', () => { }, ` Object { - "index": undefined, "parent": Object { "from": 123, "index": "123", diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index a3979ffa6e94..3ac6b623fbc8 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -75,7 +75,13 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { buildEsQuery, Filter } from '@kbn/es-query'; import { normalizeSortRequest } from './normalize_sort_request'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; -import { IIndexPattern, IndexPattern, IndexPatternField } from '../..'; +import { + AggConfigSerialized, + IIndexPattern, + IndexPattern, + IndexPatternField, + SerializedSearchSourceFields, +} from '../..'; import { AggConfigs, EsQuerySortValue, @@ -846,12 +852,26 @@ export class SearchSource { /** * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) */ - public getSerializedFields(recurse = false) { - const { filter: originalFilters, size: omit, ...searchSourceFields } = this.getFields(); - let serializedSearchSourceFields: SearchSourceFields = { + public getSerializedFields(recurse = false): SerializedSearchSourceFields { + const { + filter: originalFilters, + aggs: searchSourceAggs, + parent, + size: omit, + sort, + index, + ...searchSourceFields + } = this.getFields(); + + let serializedSearchSourceFields: SerializedSearchSourceFields = { ...searchSourceFields, - index: (searchSourceFields.index ? searchSourceFields.index.id : undefined) as any, }; + if (index) { + serializedSearchSourceFields.index = index.id; + } + if (sort) { + serializedSearchSourceFields.sort = !Array.isArray(sort) ? [sort] : sort; + } if (originalFilters) { const filters = this.getFilters(originalFilters); serializedSearchSourceFields = { @@ -859,6 +879,17 @@ export class SearchSource { filter: filters, }; } + if (searchSourceAggs) { + let aggs = searchSourceAggs; + if (typeof aggs === 'function') { + aggs = (searchSourceAggs as Function)(); + } + if (aggs instanceof AggConfigs) { + serializedSearchSourceFields.aggs = aggs.getAll().map((agg) => agg.serialize()); + } else { + serializedSearchSourceFields.aggs = aggs as AggConfigSerialized[]; + } + } if (recurse && this.getParent()) { serializedSearchSourceFields.parent = this.getParent()!.getSerializedFields(recurse); } diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index c411e53abfcd..acfdf1726316 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -5,8 +5,10 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { IAggConfigs } from 'src/plugins/data/public'; +import { AggConfigSerialized, IAggConfigs } from 'src/plugins/data/public'; +import { SerializableRecord } from '@kbn/utility-types'; import { Query } from '../..'; import { Filter } from '../../es_query'; import { IndexPattern } from '../..'; @@ -27,7 +29,7 @@ export interface ISearchStartSearchSource { * creates {@link SearchSource} based on provided serialized {@link SearchSourceFields} * @param fields */ - create: (fields?: SearchSourceFields) => Promise; + create: (fields?: SerializedSearchSourceFields) => Promise; /** * creates empty {@link SearchSource} */ @@ -112,6 +114,53 @@ export interface SearchSourceFields { parent?: SearchSourceFields; } +export interface SerializedSearchSourceFields { + type?: string; + /** + * {@link Query} + */ + query?: Query; + /** + * {@link Filter} + */ + filter?: Filter[]; + /** + * {@link EsQuerySortValue} + */ + sort?: EsQuerySortValue[]; + highlight?: SerializableRecord; + highlightAll?: boolean; + trackTotalHits?: boolean | number; + // todo: needs aggconfigs serializable type + /** + * {@link AggConfigs} + */ + aggs?: AggConfigSerialized[]; + from?: number; + size?: number; + source?: boolean | estypes.Fields; + version?: boolean; + /** + * Retrieve fields via the search Fields API + */ + fields?: SearchFieldValue[]; + /** + * Retreive fields directly from _source (legacy behavior) + * + * @deprecated It is recommended to use `fields` wherever possible. + */ + fieldsFromSource?: estypes.Fields; + /** + * {@link IndexPatternService} + */ + index?: string; + searchAfter?: EsQuerySearchAfter; + timeout?: string; + terminate_after?: number; + + parent?: SerializedSearchSourceFields; +} + export interface SearchSourceOptions { callParentStartHandlers?: boolean; } diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index a54a9c7f35e3..567a0b1d8c6d 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -187,6 +187,7 @@ export type { ISearchSource, SearchRequest, SearchSourceFields, + SerializedSearchSourceFields, // errors IEsError, Reason, diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 2cd7993e3b18..810436dc30b9 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -27,6 +27,7 @@ export type { SearchRequest, SearchSourceDependencies, SearchSourceFields, + SerializedSearchSourceFields, } from '../../common/search'; export { ES_SEARCH_STRATEGY, diff --git a/src/plugins/discover/public/utils/get_sharing_data.ts b/src/plugins/discover/public/utils/get_sharing_data.ts index b4006a691afc..e14ae252da95 100644 --- a/src/plugins/discover/public/utils/get_sharing_data.ts +++ b/src/plugins/discover/public/utils/get_sharing_data.ts @@ -9,7 +9,7 @@ import type { Capabilities } from 'kibana/public'; import type { IUiSettingsClient } from 'kibana/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; -import type { Filter, ISearchSource, SearchSourceFields } from 'src/plugins/data/common'; +import type { Filter, ISearchSource, SerializedSearchSourceFields } from 'src/plugins/data/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../common'; import type { SavedSearch, SortOrder } from '../services/saved_searches'; import { getSortForSearchSource } from '../components/doc_table'; @@ -55,7 +55,7 @@ export async function getSharingData( } return { - getSearchSource: (absoluteTime?: boolean): SearchSourceFields => { + getSearchSource: (absoluteTime?: boolean): SerializedSearchSourceFields => { const timeFilter = absoluteTime ? data.query.timefilter.timefilter.createFilter(index) : data.query.timefilter.timefilter.createRelativeFilter(index); diff --git a/src/plugins/expression_repeat_image/kibana.json b/src/plugins/expression_repeat_image/kibana.json index 5694e0160042..0df2eb984231 100755 --- a/src/plugins/expression_repeat_image/kibana.json +++ b/src/plugins/expression_repeat_image/kibana.json @@ -11,5 +11,5 @@ "ui": true, "requiredPlugins": ["expressions", "presentationUtil"], "optionalPlugins": [], - "requiredBundles": [] + "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/expression_repeat_image/public/components/repeat_image_component.tsx b/src/plugins/expression_repeat_image/public/components/repeat_image_component.tsx index 7a136b470e94..7da6735c6ce8 100644 --- a/src/plugins/expression_repeat_image/public/components/repeat_image_component.tsx +++ b/src/plugins/expression_repeat_image/public/components/repeat_image_component.tsx @@ -46,7 +46,9 @@ function setImageSize(img: HTMLImageElement, size: number) { } function createImageJSX(img: HTMLImageElement | null) { - if (!img) return null; + if (!img) { + return null; + } const params = img.width > img.height ? { heigth: img.height } : { width: img.width }; return ; } diff --git a/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx b/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx index 42f008b2570e..c727ca9562fa 100644 --- a/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx +++ b/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { Render } from '../../../../presentation_util/public/__stories__'; -import { repeatImageRenderer } from '../repeat_image_renderer'; +import { getRepeatImageRenderer } from '../repeat_image_renderer'; import { getElasticLogo, getElasticOutline, @@ -31,7 +31,7 @@ const Renderer = ({ emptyImage: elasticOutline, }; - return ; + return ; }; storiesOf('enderers/repeatImage', module).add( diff --git a/src/plugins/expression_repeat_image/public/expression_renderers/index.ts b/src/plugins/expression_repeat_image/public/expression_renderers/index.ts index 5c5625f8c773..eb161e6e0f2a 100644 --- a/src/plugins/expression_repeat_image/public/expression_renderers/index.ts +++ b/src/plugins/expression_repeat_image/public/expression_renderers/index.ts @@ -6,8 +6,4 @@ * Side Public License, v 1. */ -import { repeatImageRenderer } from './repeat_image_renderer'; - -export const renderers = [repeatImageRenderer]; - -export { repeatImageRenderer }; +export { getRepeatImageRenderer, repeatImageRendererFactory } from './repeat_image_renderer'; diff --git a/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx b/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx index 330bf16e038f..5e5bc04f317d 100644 --- a/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx +++ b/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx @@ -7,10 +7,19 @@ */ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n-react'; +import { Observable } from 'rxjs'; +import { CoreTheme } from 'kibana/public'; import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { i18n } from '@kbn/i18n'; -import { getElasticOutline, isValidUrl, withSuspense } from '../../../presentation_util/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaThemeProvider } from '../../../kibana_react/public'; +import { CoreSetup } from '../../../../core/public'; +import { + defaultTheme$, + getElasticOutline, + isValidUrl, + withSuspense, +} from '../../../presentation_util/public'; import { RepeatImageRendererConfig } from '../../common/types'; const strings = { @@ -27,32 +36,39 @@ const strings = { const LazyRepeatImageComponent = lazy(() => import('../components/repeat_image_component')); const RepeatImageComponent = withSuspense(LazyRepeatImageComponent, null); -export const repeatImageRenderer = (): ExpressionRenderDefinition => ({ - name: 'repeatImage', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render: async ( - domNode: HTMLElement, - config: RepeatImageRendererConfig, - handlers: IInterpreterRenderHandlers - ) => { - const { elasticOutline } = await getElasticOutline(); - const settings = { - ...config, - image: isValidUrl(config.image) ? config.image : elasticOutline, - emptyImage: config.emptyImage || '', - }; +export const getRepeatImageRenderer = + (theme$: Observable = defaultTheme$) => + (): ExpressionRenderDefinition => ({ + name: 'repeatImage', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: RepeatImageRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + const { elasticOutline } = await getElasticOutline(); + const settings = { + ...config, + image: isValidUrl(config.image) ? config.image : elasticOutline, + emptyImage: config.emptyImage || '', + }; + + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); + render( + + + + + , + domNode + ); + }, + }); - render( - - - , - domNode - ); - }, -}); +export const repeatImageRendererFactory = (core: CoreSetup) => + getRepeatImageRenderer(core.theme.theme$); diff --git a/src/plugins/expression_repeat_image/public/index.ts b/src/plugins/expression_repeat_image/public/index.ts index 21e8f449dcc7..4080ad4f1359 100755 --- a/src/plugins/expression_repeat_image/public/index.ts +++ b/src/plugins/expression_repeat_image/public/index.ts @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -// TODO: https://github.com/elastic/kibana/issues/110893 -/* eslint-disable @kbn/eslint/no_export_all */ - import { ExpressionRepeatImagePlugin } from './plugin'; export type { ExpressionRepeatImagePluginSetup, ExpressionRepeatImagePluginStart } from './plugin'; @@ -17,4 +14,4 @@ export function plugin() { return new ExpressionRepeatImagePlugin(); } -export * from './expression_renderers'; +export { getRepeatImageRenderer, repeatImageRendererFactory } from './expression_renderers'; diff --git a/src/plugins/expression_repeat_image/public/plugin.ts b/src/plugins/expression_repeat_image/public/plugin.ts index d71ce99eb1bd..2f275f9218d5 100755 --- a/src/plugins/expression_repeat_image/public/plugin.ts +++ b/src/plugins/expression_repeat_image/public/plugin.ts @@ -9,7 +9,7 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; import { repeatImageFunction } from '../common/expression_functions'; -import { repeatImageRenderer } from './expression_renderers'; +import { repeatImageRendererFactory } from './expression_renderers'; interface SetupDeps { expressions: ExpressionsSetup; @@ -33,7 +33,7 @@ export class ExpressionRepeatImagePlugin { public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRepeatImagePluginSetup { expressions.registerFunction(repeatImageFunction); - expressions.registerRenderer(repeatImageRenderer); + expressions.registerRenderer(repeatImageRendererFactory(core)); } public start(core: CoreStart): ExpressionRepeatImagePluginStart {} diff --git a/src/plugins/expression_reveal_image/kibana.json b/src/plugins/expression_reveal_image/kibana.json index dad7fdfe2bc5..5fb13ce31247 100755 --- a/src/plugins/expression_reveal_image/kibana.json +++ b/src/plugins/expression_reveal_image/kibana.json @@ -11,5 +11,5 @@ "ui": true, "requiredPlugins": ["expressions", "presentationUtil"], "optionalPlugins": [], - "requiredBundles": [] + "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx index 863d8d1000f3..22dd2ef4156d 100644 --- a/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx +++ b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { revealImageRenderer } from '../'; +import { getRevealImageRenderer } from '../'; import { getElasticOutline, getElasticLogo } from '../../../../presentation_util/public'; import { Render, waitFor } from '../../../../presentation_util/public/__stories__'; import { Origin } from '../../../common/types/expression_functions'; @@ -26,7 +26,7 @@ const Renderer = ({ origin: Origin.LEFT, percent: 0.45, }; - return ; + return ; }; storiesOf('renderers/revealImage', module).add( diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/index.ts b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts index 433a81884f15..959a630b08b5 100644 --- a/src/plugins/expression_reveal_image/public/expression_renderers/index.ts +++ b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts @@ -6,8 +6,4 @@ * Side Public License, v 1. */ -import { revealImageRenderer } from './reveal_image_renderer'; - -export const renderers = [revealImageRenderer]; - -export { revealImageRenderer }; +export { revealImageRendererFactory, getRevealImageRenderer } from './reveal_image_renderer'; diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx index d4dec3a8a582..6bdd01429641 100644 --- a/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx +++ b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx @@ -7,10 +7,14 @@ */ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Observable } from 'rxjs'; +import { CoreTheme } from 'kibana/public'; import { I18nProvider } from '@kbn/i18n-react'; import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { i18n } from '@kbn/i18n'; -import { withSuspense } from '../../../presentation_util/public'; +import { CoreSetup } from '../../../../core/public'; +import { KibanaThemeProvider } from '../../../kibana_react/public'; +import { withSuspense, defaultTheme$ } from '../../../presentation_util/public'; import { RevealImageRendererConfig } from '../../common/types'; export const strings = { @@ -27,25 +31,32 @@ export const strings = { const LazyRevealImageComponent = lazy(() => import('../components/reveal_image_component')); const RevealImageComponent = withSuspense(LazyRevealImageComponent, null); -export const revealImageRenderer = (): ExpressionRenderDefinition => ({ - name: 'revealImage', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render: ( - domNode: HTMLElement, - config: RevealImageRendererConfig, - handlers: IInterpreterRenderHandlers - ) => { - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); +export const getRevealImageRenderer = + (theme$: Observable = defaultTheme$) => + (): ExpressionRenderDefinition => ({ + name: 'revealImage', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: ( + domNode: HTMLElement, + config: RevealImageRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); - render( - - - , - domNode - ); - }, -}); + render( + + + + + , + domNode + ); + }, + }); + +export const revealImageRendererFactory = (core: CoreSetup) => + getRevealImageRenderer(core.theme.theme$); diff --git a/src/plugins/expression_reveal_image/public/index.ts b/src/plugins/expression_reveal_image/public/index.ts index 66512a1126b0..736e062475e6 100755 --- a/src/plugins/expression_reveal_image/public/index.ts +++ b/src/plugins/expression_reveal_image/public/index.ts @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -// TODO: https://github.com/elastic/kibana/issues/110893 -/* eslint-disable @kbn/eslint/no_export_all */ - import { ExpressionRevealImagePlugin } from './plugin'; export type { ExpressionRevealImagePluginSetup, ExpressionRevealImagePluginStart } from './plugin'; @@ -17,4 +14,4 @@ export function plugin() { return new ExpressionRevealImagePlugin(); } -export * from './expression_renderers'; +export { revealImageRendererFactory, getRevealImageRenderer } from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/public/plugin.ts b/src/plugins/expression_reveal_image/public/plugin.ts index c5e1b5c8d916..17bff3f33e8a 100755 --- a/src/plugins/expression_reveal_image/public/plugin.ts +++ b/src/plugins/expression_reveal_image/public/plugin.ts @@ -8,7 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; -import { revealImageRenderer } from './expression_renderers'; +import { revealImageRendererFactory } from './expression_renderers'; import { revealImageFunction } from '../common/expression_functions'; interface SetupDeps { @@ -33,7 +33,7 @@ export class ExpressionRevealImagePlugin { public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRevealImagePluginSetup { expressions.registerFunction(revealImageFunction); - expressions.registerRenderer(revealImageRenderer); + expressions.registerRenderer(revealImageRendererFactory(core)); } public start(core: CoreStart): ExpressionRevealImagePluginStart {} diff --git a/src/plugins/input_control_vis/public/control/create_search_source.ts b/src/plugins/input_control_vis/public/control/create_search_source.ts index 940bf2221fb9..87dec8b1d9a2 100644 --- a/src/plugins/input_control_vis/public/control/create_search_source.ts +++ b/src/plugins/input_control_vis/public/control/create_search_source.ts @@ -8,7 +8,7 @@ import { Filter } from '@kbn/es-query'; import { - SearchSourceFields, + SerializedSearchSourceFields, IndexPattern, TimefilterContract, DataPublicPluginStart, @@ -16,7 +16,7 @@ import { export async function createSearchSource( { create }: DataPublicPluginStart['search']['searchSource'], - initialState: SearchSourceFields | null, + initialState: SerializedSearchSourceFields | null, indexPattern: IndexPattern, aggs: any, useTimeFilter: boolean, diff --git a/src/plugins/input_control_vis/public/control/list_control_factory.ts b/src/plugins/input_control_vis/public/control/list_control_factory.ts index 342e05460b8f..39c5f259c273 100644 --- a/src/plugins/input_control_vis/public/control/list_control_factory.ts +++ b/src/plugins/input_control_vis/public/control/list_control_factory.ts @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { IndexPatternField, TimefilterContract, - SearchSourceFields, + SerializedSearchSourceFields, DataPublicPluginStart, } from 'src/plugins/data/public'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; @@ -127,7 +127,7 @@ export class ListControl extends Control { const fieldName = this.filterManager.fieldName; const settings = await this.getSettings(); - const initialSearchSourceState: SearchSourceFields = { + const initialSearchSourceState: SerializedSearchSourceFields = { timeout: `${settings.autocompleteTimeout}ms`, terminate_after: Number(settings.autocompleteTerminateAfter), }; diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts index 005e280fcc74..82d02882698d 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.test.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.test.ts @@ -7,6 +7,7 @@ */ jest.mock('fs/promises'); +jest.mock('crypto'); import { constants } from 'fs'; import { loggingSystemMock } from 'src/core/server/mocks'; @@ -28,6 +29,16 @@ describe('KibanaConfigWriter', () => { mockReadFile.mockResolvedValue(''); + const mockCrypto = jest.requireMock('crypto'); + mockCrypto.X509Certificate = function (cert: string) { + if (cert === 'invalid-cert') { + throw new Error('Invalid certificate'); + } + return { + fingerprint256: 'fingerprint256', + }; + }; + kibanaConfigWriter = new KibanaConfigWriter( '/some/path/kibana.yml', '/data', @@ -120,6 +131,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] ", ], @@ -186,6 +198,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.username: username elasticsearch.password: password elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] ", ], @@ -193,6 +206,18 @@ describe('KibanaConfigWriter', () => { `); }); + it('throws if it cannot parse CA certificate', async () => { + await expect( + kibanaConfigWriter.writeConfig({ + caCert: 'invalid-cert', + host: 'some-host', + serviceAccountToken: { name: 'some-token', value: 'some-value' }, + }) + ).rejects.toMatchInlineSnapshot(`[Error: Invalid certificate]`); + + expect(mockWriteFile).not.toHaveBeenCalled(); + }); + it('can successfully write elasticsearch config without CA certificate', async () => { await expect( kibanaConfigWriter.writeConfig({ @@ -250,6 +275,7 @@ describe('KibanaConfigWriter', () => { elasticsearch.hosts: [some-host] elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] ", ], @@ -303,6 +329,7 @@ describe('KibanaConfigWriter', () => { monitoring.ui.container.elasticsearch.enabled: true elasticsearch.serviceAccountToken: some-value elasticsearch.ssl.certificateAuthorities: [/data/ca_1234.crt] + xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: [some-host], ca_sha256: fingerprint256}] ", ], diff --git a/src/plugins/interactive_setup/server/kibana_config_writer.ts b/src/plugins/interactive_setup/server/kibana_config_writer.ts index 949bc25ddd25..af177fee33bc 100644 --- a/src/plugins/interactive_setup/server/kibana_config_writer.ts +++ b/src/plugins/interactive_setup/server/kibana_config_writer.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { X509Certificate } from 'crypto'; import { constants } from 'fs'; import fs from 'fs/promises'; import yaml from 'js-yaml'; @@ -30,6 +31,16 @@ export type WriteConfigParameters = { | {} ); +interface FleetOutputConfig { + id: string; + name: string; + is_default: boolean; + is_default_monitoring: boolean; + type: 'elasticsearch'; + hosts: string[]; + ca_sha256: string; +} + export class KibanaConfigWriter { constructor( private readonly configPath: string, @@ -61,7 +72,9 @@ export class KibanaConfigWriter { */ public async writeConfig(params: WriteConfigParameters) { const caPath = path.join(this.dataDirectoryPath, `ca_${Date.now()}.crt`); - const config: Record = { 'elasticsearch.hosts': [params.host] }; + const config: Record = { + 'elasticsearch.hosts': [params.host], + }; if ('serviceAccountToken' in params && params.serviceAccountToken) { config['elasticsearch.serviceAccountToken'] = params.serviceAccountToken.value; } else if ('username' in params && params.username) { @@ -72,6 +85,21 @@ export class KibanaConfigWriter { config['elasticsearch.ssl.certificateAuthorities'] = [caPath]; } + // If a certificate is passed configure Fleet default output + if (params.caCert) { + try { + config['xpack.fleet.outputs'] = KibanaConfigWriter.getFleetDefaultOutputConfig( + params.caCert, + params.host + ); + } catch (err) { + this.logger.error( + `Failed to generate Fleet default output: ${getDetailedErrorMessage(err)}.` + ); + throw err; + } + } + // Load and parse existing configuration file to check if it already has values for the config // entries we want to write. const existingConfig = await this.loadAndParseKibanaConfig(); @@ -152,6 +180,28 @@ export class KibanaConfigWriter { return { raw: rawConfig, parsed: parsedConfig }; } + /** + * Build config for Fleet outputs + * @param caCert + * @param host + */ + private static getFleetDefaultOutputConfig(caCert: string, host: string): FleetOutputConfig[] { + const cert = new X509Certificate(caCert); + const certFingerprint = cert.fingerprint256; + + return [ + { + id: 'fleet-default-output', + name: 'default', + is_default: true, + is_default_monitoring: true, + type: 'elasticsearch', + hosts: [host], + ca_sha256: certFingerprint, + }, + ]; + } + /** * Comments out all non-commented entries in the Kibana configuration file. * @param rawConfig Content of the Kibana configuration file. diff --git a/src/plugins/presentation_util/common/index.ts b/src/plugins/presentation_util/common/index.ts index 4510a0aac5a0..a84a78c823a5 100644 --- a/src/plugins/presentation_util/common/index.ts +++ b/src/plugins/presentation_util/common/index.ts @@ -12,4 +12,10 @@ export const PLUGIN_ID = 'presentationUtil'; export const PLUGIN_NAME = 'presentationUtil'; +/** + * The unique identifier for the Expressions Language for use in the ExpressionInput + * and CodeEditor components. + */ +export const EXPRESSIONS_LANGUAGE_ID = 'kibana-expressions'; + export * from './labs'; diff --git a/src/plugins/presentation_util/kibana.json b/src/plugins/presentation_util/kibana.json index 210937b335e5..32460a845515 100644 --- a/src/plugins/presentation_util/kibana.json +++ b/src/plugins/presentation_util/kibana.json @@ -9,7 +9,16 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "extraPublicDirs": ["common/lib"], - "requiredPlugins": ["savedObjects", "data", "dataViews", "embeddable", "kibanaReact"], + "extraPublicDirs": [ + "common/lib" + ], + "requiredPlugins": [ + "savedObjects", + "data", + "dataViews", + "embeddable", + "kibanaReact", + "expressions" + ], "optionalPlugins": [] } diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.ts b/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts similarity index 98% rename from x-pack/plugins/canvas/common/lib/autocomplete.ts rename to src/plugins/presentation_util/public/components/expression_input/autocomplete.ts index 88fb6b052b95..5f0c9cab6215 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.ts +++ b/src/plugins/presentation_util/public/components/expression_input/autocomplete.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { uniq } from 'lodash'; @@ -15,9 +16,9 @@ import { ExpressionFunction, ExpressionFunctionParameter, getByAlias, -} from '../../../../../src/plugins/expressions/common'; +} from '../../../../expressions/common'; -const MARKER = 'CANVAS_SUGGESTION_MARKER'; +const MARKER = 'EXPRESSIONS_SUGGESTION_MARKER'; interface BaseSuggestion { text: string; @@ -25,11 +26,6 @@ interface BaseSuggestion { end: number; } -export interface FunctionSuggestion extends BaseSuggestion { - type: 'function'; - fnDef: ExpressionFunction; -} - interface ArgSuggestionValue extends Omit { name: string; } @@ -43,8 +39,6 @@ interface ValueSuggestion extends BaseSuggestion { type: 'value'; } -export type AutocompleteSuggestion = FunctionSuggestion | ArgSuggestion | ValueSuggestion; - interface FnArgAtPosition { ast: ExpressionASTWithMeta; fnIndex: number; @@ -57,6 +51,7 @@ interface FnArgAtPosition { // If this function is a sub-expression function, we need the parent function and argument // name to determine the return type of the function parentFn?: string; + // If this function is a sub-expression function, the context could either be local or it // could be the parent's previous function. contextFn?: string | null; @@ -101,6 +96,13 @@ type ExpressionASTWithMeta = ASTMetaInformation< > >; +export interface FunctionSuggestion extends BaseSuggestion { + type: 'function'; + fnDef: ExpressionFunction; +} + +export type AutocompleteSuggestion = FunctionSuggestion | ArgSuggestion | ValueSuggestion; + // Typeguard for checking if ExpressionArg is a new expression function isExpression( maybeExpression: ExpressionArgASTWithMeta diff --git a/src/plugins/presentation_util/public/components/expression_input/constants.ts b/src/plugins/presentation_util/public/components/expression_input/constants.ts new file mode 100644 index 000000000000..f937d55cbf9b --- /dev/null +++ b/src/plugins/presentation_util/public/components/expression_input/constants.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CodeEditorProps } from '../../../../kibana_react/public'; + +export const LANGUAGE_CONFIGURATION = { + autoClosingPairs: [ + { + open: '{', + close: '}', + }, + ], +}; + +export const CODE_EDITOR_OPTIONS: CodeEditorProps['options'] = { + scrollBeyondLastLine: false, + quickSuggestions: true, + minimap: { + enabled: false, + }, + wordWrap: 'on', + wrappingIndent: 'indent', +}; diff --git a/src/plugins/presentation_util/public/components/expression_input/expression_input.stories.tsx b/src/plugins/presentation_util/public/components/expression_input/expression_input.stories.tsx new file mode 100644 index 000000000000..648171959791 --- /dev/null +++ b/src/plugins/presentation_util/public/components/expression_input/expression_input.stories.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import { Meta } from '@storybook/react'; + +import { ExpressionFunction, ExpressionFunctionParameter, Style } from 'src/plugins/expressions'; +import { ExpressionInput } from '../expression_input'; +import { registerExpressionsLanguage } from './language'; + +const content: ExpressionFunctionParameter<'string'> = { + name: 'content', + required: false, + help: 'A string of text that contains Markdown. To concatenate, pass the `string` function multiple times.', + types: ['string'], + default: '', + aliases: ['_', 'expression'], + multi: true, + resolve: false, + options: [], + accepts: () => true, +}; + +const font: ExpressionFunctionParameter