diff --git a/.buildkite/pipelines/pull_request/osquery_cypress.yml b/.buildkite/pipelines/pull_request/osquery_cypress.yml new file mode 100644 index 0000000000000..766d28e0877c7 --- /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 d0f38dc773357..ab125d4f73377 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 0000000000000..a23d41c4f8d4d --- /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/.github/CODEOWNERS b/.github/CODEOWNERS index c398316e634b9..a64ab63494b35 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,7 +22,6 @@ # Vis Editors /x-pack/plugins/lens/ @elastic/kibana-vis-editors -/src/plugins/advanced_settings/ @elastic/kibana-vis-editors /src/plugins/charts/ @elastic/kibana-vis-editors /src/plugins/vis_default_editor/ @elastic/kibana-vis-editors /src/plugins/vis_types/metric/ @elastic/kibana-vis-editors @@ -263,6 +262,7 @@ /src/plugins/home/server/*.ts @elastic/kibana-core /src/plugins/home/server/services/ @elastic/kibana-core /src/plugins/kibana_overview/ @elastic/kibana-core +/src/plugins/advanced_settings/ @elastic/kibana-core /x-pack/plugins/global_search_bar/ @elastic/kibana-core #CC# /src/core/server/csp/ @elastic/kibana-core #CC# /src/plugins/saved_objects/ @elastic/kibana-core diff --git a/dev_docs/key_concepts/performance.mdx b/dev_docs/key_concepts/performance.mdx index 0201c7774f854..5d955c789ddeb 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 9583e195d1c82..a9d8cd7c6ec1c 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 a7ce86ea46359..54a5c319c6222 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/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 89a21b0424ed0..fa5801e622706 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -233,7 +233,7 @@ To get the most from the search feature, follow these tips: |Search by type |`type:dashboard` -Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` +Available types: `application`, `canvas-workpad`, `dashboard`, `data-view`, `lens`, `maps`, `query`, `search`, `visualization` |Search by tag |`tag:mytagname` + diff --git a/package.json b/package.json index a6dfd03893c81..374ccee71ec6a 100644 --- a/package.json +++ b/package.json @@ -79,9 +79,12 @@ "**/chokidar": "^3.4.3", "**/deepmerge": "^4.2.2", "**/fast-deep-equal": "^3.1.1", + "**/handlebars/uglify-js": "^3.14.3", "**/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", "**/pdfkit/crypto-js": "4.0.0", @@ -433,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", @@ -562,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", @@ -691,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", @@ -746,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", @@ -783,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 8208496f7d800..96b1846147689 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 c6611e71e35ab..66e00706e9e58 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 70de78b7617c9..ed6082527bab9 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 a3b6880c3293c..d6a0e91b9b429 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 8635421beb0a1..f9db84f255ec6 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 c0b75ab491ac0..0353b2d16be7b 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 0f35aab461078..81ee6d770103c 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 8fa6cd3c232fa..96bf21906ed4a 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 f980fe83b9596..728cb0690834d 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 26d2030d1b0ba..70d8d659c99fe 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 e5f1de4d07f63..dd81e8318e9d9 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 cc03c81070745..a389086c9ee3c 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 609fe6d00f173..be74c363a7acf 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 9f8a9f34061d2..6e7e10d4dd816 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 c2f82d65d3318..c4d256e7672ab 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 307d47af9ac50..e4c80a0783b5d 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 64c1c4ce2fa9f..f92d505c058ed 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 da16dbc5e69e8..5f2870fb6e244 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 46a532cdefef4..ab692b146e7f6 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 9be58f1b71861..0d17525016043 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 d4393791a74fa..c599b2f719408 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/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 078786abb8c64..52b1f816090df 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -25,6 +25,7 @@ export const LICENSE_ALLOWED = [ '(MIT OR WTFPL)', '(Unlicense OR Apache-2.0)', 'AFLv2.1', + '(AFL-2.1 OR BSD-3-Clause)', 'Apache 2.0', 'Apache License, v2.0', 'Apache License, Version 2.0', 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 c6c8bb4d26f9e..3d2300940ac06 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 dfcd1b12cb62f..de32836ced124 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 d2fd10f14b633..1785e55acc792 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 6729025943b95..c4b39773c4401 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 f34f32a0bff92..6c7d08a4f2b50 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 1afd2d98782a2..87e249acab8b1 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 a3979ffa6e943..3ac6b623fbc80 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 c411e53abfcd2..acfdf17263169 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 a54a9c7f35e3f..567a0b1d8c6d9 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 2cd7993e3b183..810436dc30b98 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/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx index 7f78bb1c698ab..a31557124d49a 100644 --- a/src/plugins/discover/public/application/context/context_app.test.tsx +++ b/src/plugins/discover/public/application/context/context_app.test.tsx @@ -19,6 +19,7 @@ import { DiscoverServices } from '../../build_services'; import { indexPatternsMock } from '../../__mocks__/index_patterns'; import { act } from 'react-dom/test-utils'; import { uiSettingsMock } from '../../__mocks__/ui_settings'; +import { themeServiceMock } from '../../../../../core/public/mocks'; const mockFilterManager = createFilterManagerMock(); const mockNavigationPlugin = { ui: { TopNavMenu: mockTopNavMenu } }; @@ -60,7 +61,10 @@ describe('ContextApp test', () => { indexPatterns: indexPatternsMock, toastNotifications: { addDanger: () => {} }, navigation: mockNavigationPlugin, - core: { notifications: { toasts: [] } }, + core: { + notifications: { toasts: [] }, + theme: { theme$: themeServiceMock.createStartContract().theme$ }, + }, history: () => {}, fieldFormats: { getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })), diff --git a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts index cd7bcd810dc39..0fc8bd99c021d 100644 --- a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts +++ b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.test.ts @@ -21,6 +21,7 @@ import { import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield'; import { createContextSearchSourceStub } from '../services/_stubs'; import { IndexPattern } from '../../../../../data_views/common'; +import { themeServiceMock } from '../../../../../../core/public/mocks'; const mockFilterManager = createFilterManagerMock(); @@ -60,7 +61,10 @@ const initDefaults = (tieBreakerFields: string[], indexPatternId = 'the-index-pa }, }, toastNotifications: { addDanger: dangerNotification }, - core: { notifications: { toasts: [] } }, + core: { + notifications: { toasts: [] }, + theme: { theme$: themeServiceMock.createStartContract().theme$ }, + }, history: () => {}, filterManager: mockFilterManager, uiSettings: { diff --git a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx index e5ed24d475497..fc5718abb43f0 100644 --- a/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx +++ b/src/plugins/discover/public/application/context/utils/use_context_app_fetch.tsx @@ -11,7 +11,7 @@ import { CONTEXT_TIE_BREAKER_FIELDS_SETTING } from '../../../../common'; import { DiscoverServices } from '../../../build_services'; import { fetchAnchor } from '../services/anchor'; import { fetchSurroundingDocs, SurrDocType } from '../services/context'; -import { MarkdownSimple, toMountPoint } from '../../../../../kibana_react/public'; +import { MarkdownSimple, toMountPoint, wrapWithTheme } from '../../../../../kibana_react/public'; import { IndexPattern, SortDirection } from '../../../../../data/public'; import { ContextFetchState, @@ -42,7 +42,8 @@ export function useContextAppFetch({ useNewFieldsApi, services, }: ContextAppFetchProps) { - const { uiSettings: config, data, toastNotifications, filterManager } = services; + const { uiSettings: config, data, toastNotifications, filterManager, core } = services; + const { theme$ } = core.theme; const searchSource = useMemo(() => { return data.search.searchSource.createEmpty(); @@ -70,11 +71,14 @@ export function useContextAppFetch({ toastNotifications.addDanger({ title: errorTitle, text: toMountPoint( - - {i18n.translate('discover.context.invalidTieBreakerFiledSetting', { - defaultMessage: 'Invalid tie breaker field setting', - })} - + wrapWithTheme( + + {i18n.translate('discover.context.invalidTieBreakerFiledSetting', { + defaultMessage: 'Invalid tie breaker field setting', + })} + , + theme$ + ) ), }); return; @@ -93,7 +97,7 @@ export function useContextAppFetch({ setState(createError('anchorStatus', FailureReason.UNKNOWN, error)); toastNotifications.addDanger({ title: errorTitle, - text: toMountPoint({error.message}), + text: toMountPoint(wrapWithTheme({error.message}, theme$)), }); } }, [ @@ -104,6 +108,7 @@ export function useContextAppFetch({ anchorId, searchSource, useNewFieldsApi, + theme$, ]); const fetchSurroundingRows = useCallback( @@ -135,7 +140,9 @@ export function useContextAppFetch({ setState(createError(statusKey, FailureReason.UNKNOWN, error)); toastNotifications.addDanger({ title: errorTitle, - text: toMountPoint({error.message}), + text: toMountPoint( + wrapWithTheme({error.message}, theme$) + ), }); } }, @@ -148,6 +155,7 @@ export function useContextAppFetch({ indexPattern, toastNotifications, useNewFieldsApi, + theme$, ] ); diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index f6c7d60ed7db8..55407835c0a4b 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -8,11 +8,11 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; import { discoverRouter } from './discover_router'; -import { toMountPoint } from '../../../kibana_react/public'; +import { toMountPoint, wrapWithTheme } from '../../../kibana_react/public'; export const renderApp = (element: HTMLElement) => { const services = getServices(); - const { history: getHistory, capabilities, chrome, data } = services; + const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); if (!capabilities.discover.save) { @@ -26,7 +26,9 @@ export const renderApp = (element: HTMLElement) => { iconType: 'glasses', }); } - const unmount = toMountPoint(discoverRouter(services, history))(element); + const unmount = toMountPoint(wrapWithTheme(discoverRouter(services, history), core.theme.theme$))( + element + ); return () => { unmount(); diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts index 4b9d48a92e0f5..6e93bc40f7d7f 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.ts @@ -52,6 +52,7 @@ export const getTopNavLinks = ({ openOptionsPopover({ I18nContext: services.core.i18n.Context, anchorElement, + theme$: services.core.theme.theme$, }), testId: 'discoverOptionsButton', }; @@ -95,6 +96,7 @@ export const getTopNavLinks = ({ showOpenSearchPanel({ onOpenSavedSearch, I18nContext: services.core.i18n.Context, + theme$: services.core.theme.theme$, }), }; diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx index 0d359c865220f..ea0cd804efec0 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nStart } from 'kibana/public'; +import { CoreTheme, I18nStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -22,8 +22,10 @@ import { EuiTextAlign, } from '@elastic/eui'; import './open_options_popover.scss'; +import { Observable } from 'rxjs'; import { DOC_TABLE_LEGACY } from '../../../../../common'; import { getServices } from '../../../../kibana_services'; +import { KibanaThemeProvider } from '../../../../../../kibana_react/public'; const container = document.createElement('div'); let isOpen = false; @@ -125,9 +127,11 @@ function onClose() { export function openOptionsPopover({ I18nContext, anchorElement, + theme$, }: { I18nContext: I18nStart['Context']; anchorElement: HTMLElement; + theme$: Observable; }) { if (isOpen) { onClose(); @@ -139,7 +143,9 @@ export function openOptionsPopover({ const element = ( - + + + ); ReactDOM.render(element, container); diff --git a/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx b/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx index 1a9bfd7e30c57..d506de357675a 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/show_open_search_panel.tsx @@ -8,17 +8,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nStart } from 'kibana/public'; +import { CoreTheme, I18nStart } from 'kibana/public'; +import { Observable } from 'rxjs'; import { OpenSearchPanel } from './open_search_panel'; +import { KibanaThemeProvider } from '../../../../../../kibana_react/public'; let isOpen = false; export function showOpenSearchPanel({ I18nContext, onOpenSavedSearch, + theme$, }: { I18nContext: I18nStart['Context']; onOpenSavedSearch: (id: string) => void; + theme$: Observable; }) { if (isOpen) { return; @@ -35,7 +39,9 @@ export function showOpenSearchPanel({ document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); diff --git a/src/plugins/discover/public/application/not_found/not_found_route.tsx b/src/plugins/discover/public/application/not_found/not_found_route.tsx index 80e4e5c8057f6..7b42e85584428 100644 --- a/src/plugins/discover/public/application/not_found/not_found_route.tsx +++ b/src/plugins/discover/public/application/not_found/not_found_route.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { Redirect } from 'react-router-dom'; -import { toMountPoint } from '../../../../kibana_react/public'; +import { toMountPoint, wrapWithTheme } from '../../../../kibana_react/public'; import { DiscoverServices } from '../../build_services'; import { getUrlTracker } from '../../kibana_services'; @@ -39,17 +39,20 @@ export function NotFoundRoute(props: NotFoundRouteProps) { bannerId = core.overlays.banners.replace( bannerId, toMountPoint( - -

- -

-
+ wrapWithTheme( + +

+ +

+
, + core.theme.theme$ + ) ) ); @@ -59,7 +62,7 @@ export function NotFoundRoute(props: NotFoundRouteProps) { core.overlays.banners.remove(bannerId); } }, 15000); - }, [core.overlays.banners, history, urlForwarding]); + }, [core.overlays.banners, history, urlForwarding, core.theme.theme$]); return ; } diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 4a7f0b1c36868..6d7e515e33fa4 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -48,6 +48,7 @@ import { VIEW_MODE } from '../components/view_mode_toggle'; import { updateSearchSource } from './utils/update_search_source'; import { FieldStatsTableSavedSearchEmbeddable } from '../application/main/components/field_stats_table'; import { ElasticSearchHit } from '../types'; +import { KibanaThemeProvider } from '../../../kibana_react/public'; export type SearchProps = Partial & Partial & { @@ -391,15 +392,17 @@ export class SavedSearchEmbeddable Array.isArray(searchProps.columns) ) { ReactDOM.render( - , + + + , domNode ); return; @@ -410,7 +413,14 @@ export class SavedSearchEmbeddable useLegacyTable, refs: domNode, }; - ReactDOM.render(, domNode); + if (searchProps.services) { + ReactDOM.render( + + + , + domNode + ); + } } public reload() { diff --git a/src/plugins/discover/public/utils/get_sharing_data.ts b/src/plugins/discover/public/utils/get_sharing_data.ts index b4006a691afca..e14ae252da95e 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_metric/kibana.json b/src/plugins/expression_metric/kibana.json index 2aaef04e3bec3..3d844fa4de9fc 100755 --- a/src/plugins/expression_metric/kibana.json +++ b/src/plugins/expression_metric/kibana.json @@ -10,5 +10,6 @@ "server": true, "ui": true, "requiredPlugins": ["expressions", "presentationUtil"], - "optionalPlugins": [] + "optionalPlugins": [], + "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx index 0e04c32f52ba2..5835730e35f9b 100644 --- a/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx +++ b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx @@ -9,7 +9,7 @@ import React, { CSSProperties } from 'react'; import { storiesOf } from '@storybook/react'; import { Style } from 'src/plugins/expressions'; -import { metricRenderer } from '../metric_renderer'; +import { getMetricRenderer } from '../metric_renderer'; import { Render } from '../../../../presentation_util/public/__stories__'; import { MetricRendererConfig } from '../../../common'; @@ -45,7 +45,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with number metric', () => { const config: MetricRendererConfig = { @@ -55,7 +55,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with string metric', () => { const config: MetricRendererConfig = { @@ -65,7 +65,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with label', () => { const config: MetricRendererConfig = { @@ -75,7 +75,7 @@ storiesOf('renderers/Metric', module) label: 'Average price', metricFormat: '', }; - return ; + return ; }) .add('with number metric and a specified format', () => { const config: MetricRendererConfig = { @@ -85,7 +85,7 @@ storiesOf('renderers/Metric', module) label: 'Average price', metricFormat: '0.00%', }; - return ; + return ; }) .add('with formatted string metric and a specified format', () => { const config: MetricRendererConfig = { @@ -95,7 +95,7 @@ storiesOf('renderers/Metric', module) label: 'Total Revenue', metricFormat: '$0a', }; - return ; + return ; }) .add('with invalid metricFont', () => { const config: MetricRendererConfig = { @@ -105,5 +105,5 @@ storiesOf('renderers/Metric', module) label: 'Total Revenue', metricFormat: '$0a', }; - return ; + return ; }); diff --git a/src/plugins/expression_metric/public/expression_renderers/index.ts b/src/plugins/expression_metric/public/expression_renderers/index.ts index b77e0bb76f1fd..c8d6fa08147ea 100644 --- a/src/plugins/expression_metric/public/expression_renderers/index.ts +++ b/src/plugins/expression_metric/public/expression_renderers/index.ts @@ -6,8 +6,4 @@ * Side Public License, v 1. */ -import { metricRenderer } from './metric_renderer'; - -export const renderers = [metricRenderer]; - -export { metricRenderer }; +export { metricRendererFactory, getMetricRenderer } from './metric_renderer'; diff --git a/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx index 02c910640edeb..6a11910d4f26f 100644 --- a/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx +++ b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx @@ -6,10 +6,14 @@ * Side Public License, v 1. */ import React, { CSSProperties, lazy } from 'react'; +import { Observable } from 'rxjs'; +import { CoreTheme } from 'kibana/public'; import { render, unmountComponentAtNode } from 'react-dom'; 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 { MetricRendererConfig } from '../../common/types'; const strings = { @@ -26,30 +30,36 @@ const strings = { const LazyMetricComponent = lazy(() => import('../components/metric_component')); const MetricComponent = withSuspense(LazyMetricComponent); -export const metricRenderer = (): ExpressionRenderDefinition => ({ - name: 'metric', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render: async ( - domNode: HTMLElement, - config: MetricRendererConfig, - handlers: IInterpreterRenderHandlers - ) => { - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); +export const getMetricRenderer = + (theme$: Observable = defaultTheme$) => + (): ExpressionRenderDefinition => ({ + name: 'metric', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: MetricRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); - render( - , - domNode, - () => handlers.done() - ); - }, -}); + render( + + + , + domNode, + () => handlers.done() + ); + }, + }); + +export const metricRendererFactory = (core: CoreSetup) => getMetricRenderer(core.theme.theme$); diff --git a/src/plugins/expression_metric/public/index.ts b/src/plugins/expression_metric/public/index.ts index 87499f279524d..8a23c2319a3c2 100755 --- a/src/plugins/expression_metric/public/index.ts +++ b/src/plugins/expression_metric/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 { ExpressionMetricPlugin } from './plugin'; export type { ExpressionMetricPluginSetup, ExpressionMetricPluginStart } from './plugin'; @@ -17,4 +14,4 @@ export function plugin() { return new ExpressionMetricPlugin(); } -export * from './expression_renderers'; +export { metricRendererFactory, getMetricRenderer } from './expression_renderers'; diff --git a/src/plugins/expression_metric/public/plugin.ts b/src/plugins/expression_metric/public/plugin.ts index 8711a824fb7b5..6830fd904751c 100755 --- a/src/plugins/expression_metric/public/plugin.ts +++ b/src/plugins/expression_metric/public/plugin.ts @@ -9,7 +9,7 @@ import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; import { metricFunction } from '../common/expression_functions'; -import { metricRenderer } from './expression_renderers'; +import { metricRendererFactory } from './expression_renderers'; interface SetupDeps { expressions: ExpressionsSetup; @@ -27,7 +27,7 @@ export class ExpressionMetricPlugin { public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionMetricPluginSetup { expressions.registerFunction(metricFunction); - expressions.registerRenderer(metricRenderer); + expressions.registerRenderer(metricRendererFactory(core)); } public start(core: CoreStart): ExpressionMetricPluginStart {} diff --git a/src/plugins/expression_repeat_image/kibana.json b/src/plugins/expression_repeat_image/kibana.json index 5694e0160042c..0df2eb9842312 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 7a136b470e943..7da6735c6ce86 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 42f008b2570ea..c727ca9562fad 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 5c5625f8c7730..eb161e6e0f2a3 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 330bf16e038fa..5e5bc04f317d2 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 21e8f449dcc70..4080ad4f1359f 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 d71ce99eb1bd1..2f275f9218d50 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 dad7fdfe2bc5f..5fb13ce31247b 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 863d8d1000f38..22dd2ef4156df 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 433a81884f157..959a630b08b51 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 d4dec3a8a5825..6bdd014296419 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 66512a1126b06..736e062475e6a 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 c5e1b5c8d916f..17bff3f33e8ac 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 940bf2221fb94..87dec8b1d9a24 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 342e05460b8f2..39c5f259c2735 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 005e280fcc744..82d02882698d4 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 949bc25ddd253..af177fee33bce 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 4510a0aac5a0b..a84a78c823a5f 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 210937b335e50..32460a8455152 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 88fb6b052b957..5f0c9cab6215c 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 0000000000000..f937d55cbf9bb --- /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 0000000000000..648171959791f --- /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