diff --git a/.backportrc.json b/.backportrc.json index 59a101195bef78..f9d0f001c35f62 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.14", "7.13", "7.12", "7.11", @@ -31,7 +32,7 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.14.0$": "7.x", + "^v7.15.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.eslintrc.js b/.eslintrc.js index 2eea41984b30e8..09de32a91bca3c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -445,6 +445,7 @@ module.exports = { '(src|x-pack)/plugins/**/(public|server)/**/*', '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', + '!(src|x-pack)/plugins/**/__stories__/index.{js,mjs,ts,tsx}', ], allowSameFolder: true, errorMessage: 'Plugins may only import from top-level public and server modules.', diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 726e4257a5aac0..1ea9e5a5a75bc1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,7 +12,7 @@ Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) -- [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the [cloud](https://github.com/elastic/cloud) and added to the [docker list](https://github.com/elastic/kibana/blob/c29adfef29e921cc447d2a5ed06ac2047ceab552/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker) +- [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/master/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) diff --git a/.i18nrc.json b/.i18nrc.json index 0926f737227315..390e5e917d08e7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -16,6 +16,7 @@ "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", "expressions": "src/plugins/expressions", + "expressionRevealImage": "src/plugins/expression_reveal_image", "inputControl": "src/plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index acb62043a15ca7..ebf7bbc8488ac8 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -10,15 +10,15 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # Fetch Node.js rules http_archive( name = "build_bazel_rules_nodejs", - sha256 = "4a5d654a4ccd4a4c24eca5d319d85a88a650edf119601550c95bf400c8cc897e", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.5.1/rules_nodejs-3.5.1.tar.gz"], + sha256 = "0fa2d443571c9e02fcb7363a74ae591bdcce2dd76af8677a95965edf329d778a", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.6.0/rules_nodejs-3.6.0.tar.gz"], ) # Now that we have the rules let's import from them to complete the work load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install") # Assure we have at least a given rules_nodejs version -check_rules_nodejs_version(minimum_version_string = "3.5.1") +check_rules_nodejs_version(minimum_version_string = "3.6.0") # Setup the Node.js toolchain for the architectures we want to support # diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc deleted file mode 100644 index 348d15f39ad76e..00000000000000 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ /dev/null @@ -1,42 +0,0 @@ -[role="xpack"] -[[workpad-share-options]] -== Share your workpad - -When you've finished your workpad, you can share it outside of {kib}. - -For information on how to create PDFs and POST URLs, refer to <>. - -[float] -[[export-single-workpad]] -=== Export workpads - -Create a JSON file of your workpad that you can export outside of {kib}. - -To begin, click *Share > Download as JSON*. - -[role="screenshot"] -image::images/canvas-export-workpad.png[Export single workpad through JSON, from Share dropdown] - -Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*. - -[float] -[[add-workpad-website]] -=== Share the workpad on a website - -beta[] *Canvas* allows you to create _shareables_, which are workpads that you download and securely share on any website. -To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. - -. Click *Share > Share on a website*. - -. Follow the *Share on a website* instructions. - -. To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters. -+ -To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. *Canvas* does not display elements that manipulate the data on the workpad. -+ -[role="screenshot"] -image::canvas/images/canvas-embed_workpad.gif[Image showing how to share the workpad on a website] -+ -NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website. - -. To change the settings, click the settings icon, then choose the settings you want to use. diff --git a/docs/user/reporting/development/csv-integration.asciidoc b/docs/developer/architecture/development/csv-integration.asciidoc similarity index 100% rename from docs/user/reporting/development/csv-integration.asciidoc rename to docs/developer/architecture/development/csv-integration.asciidoc diff --git a/docs/user/reporting/development/index.asciidoc b/docs/developer/architecture/development/index.asciidoc similarity index 100% rename from docs/user/reporting/development/index.asciidoc rename to docs/developer/architecture/development/index.asciidoc diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/developer/architecture/development/pdf-integration.asciidoc similarity index 100% rename from docs/user/reporting/development/pdf-integration.asciidoc rename to docs/developer/architecture/development/pdf-integration.asciidoc diff --git a/docs/developer/architecture/index.asciidoc b/docs/developer/architecture/index.asciidoc index 1a0e7bab2f8f8f..90a0972d65f2fc 100644 --- a/docs/developer/architecture/index.asciidoc +++ b/docs/developer/architecture/index.asciidoc @@ -24,6 +24,7 @@ A few notable services are called out below. * <> * <> * <> +* <> include::kibana-platform-plugin-api.asciidoc[leveloffset=+1] @@ -52,3 +53,5 @@ include::security/index.asciidoc[leveloffset=+1] include::add-data-tutorials.asciidoc[leveloffset=+1] include::development-visualize-index.asciidoc[leveloffset=+1] + +include::development/index.asciidoc[leveloffset=+1] diff --git a/docs/developer/contributing/development-package-tests.asciidoc b/docs/developer/contributing/development-package-tests.asciidoc index 10c09d6cae8c07..7883ce2d832096 100644 --- a/docs/developer/contributing/development-package-tests.asciidoc +++ b/docs/developer/contributing/development-package-tests.asciidoc @@ -36,7 +36,7 @@ pip3 install --user ansible ``` # Build distributions -node scripts/build --all-platforms --debug --no-oss +node scripts/build --all-platforms --debug cd test/package diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 7d708e17ae1161..b656405b173d82 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -77,6 +77,7 @@ yarn kbn watch-bazel - @kbn/dev-utils - @kbn/docs-utils - @kbn/es +- @kbn/es-archiver - @kbn/eslint-import-resolver-kibana - @kbn/eslint-plugin-eslint - @kbn/expect @@ -107,6 +108,7 @@ yarn kbn watch-bazel - @kbn/std - @kbn/storybook - @kbn/telemetry-utils +- @kbn/test - @kbn/test-subj-selector - @kbn/tinymath - @kbn/ui-framework diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 231e089950a28e..b4be27eee5ed2d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -72,6 +72,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module. +|{kib-repo}blob/{branch}/src/plugins/expression_reveal_image/README.md[expressionRevealImage] +|Expression Reveal Image plugin adds a revealImage function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image. + + |<> |Expression pipeline is a chain of functions that *pipe* its output to the input of the next function. Functions can be configured using arguments provided diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 63d791db452d04..3650fe970d8fcf 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -28,9 +28,13 @@ readonly links: { readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; + readonly suricataModule: string; + readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; + readonly auditdModule: string; + readonly systemModule: string; }; readonly metricbeat: { readonly base: string; @@ -47,6 +51,9 @@ readonly links: { readonly heartbeat: { readonly base: string; }; + readonly libbeat: { + readonly getStarted: string; + }; readonly logstash: { readonly base: string; }; @@ -123,6 +130,10 @@ readonly links: { readonly siem: { readonly guide: string; readonly gettingStarted: string; + readonly ml: string; + readonly ruleChangeLog: string; + readonly detectionsReq: string; + readonly networkMap: string; }; readonly query: { readonly eql: string; @@ -201,5 +212,8 @@ readonly links: { upgradeElasticAgent: string; upgradeElasticAgent712lower: string; }>; + readonly ecs: { + readonly guide: string; + }; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 947eece498130e..4f66cc9a2c10f9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
} | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index 80c321ce6b3209..d06ce1b2ef2bc0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -13,11 +13,11 @@ esFilters: { FILTERS: typeof FILTERS; FilterStateStore: typeof FilterStateStore; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; - buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; + buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; + buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; + buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; - buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; + buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter; isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter; isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md deleted file mode 100644 index f99e7ba8b967ec..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [lang](./kibana-plugin-plugins-data-public.ifieldtype.lang.md) - -## IFieldType.lang property - -Signature: - -```typescript -lang?: estypes.ScriptLanguage; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md index 29377ff8fd392a..e1acea53ea5e01 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md @@ -12,7 +12,7 @@ Signature: ```typescript -export interface IFieldType +export interface IFieldType extends IndexPatternFieldBase ``` ## Properties @@ -26,15 +26,9 @@ export interface IFieldType | [esTypes](./kibana-plugin-plugins-data-public.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-public.ifieldtype.filterable.md) | boolean | | | [format](./kibana-plugin-plugins-data-public.ifieldtype.format.md) | any | | -| [lang](./kibana-plugin-plugins-data-public.ifieldtype.lang.md) | estypes.ScriptLanguage | | -| [name](./kibana-plugin-plugins-data-public.ifieldtype.name.md) | string | | | [readFromDocValues](./kibana-plugin-plugins-data-public.ifieldtype.readfromdocvalues.md) | boolean | | -| [script](./kibana-plugin-plugins-data-public.ifieldtype.script.md) | string | | -| [scripted](./kibana-plugin-plugins-data-public.ifieldtype.scripted.md) | boolean | | | [searchable](./kibana-plugin-plugins-data-public.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-public.ifieldtype.sortable.md) | boolean | | -| [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md) | IFieldSubType | | | [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | -| [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-public.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md deleted file mode 100644 index 1c01484372fd37..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [name](./kibana-plugin-plugins-data-public.ifieldtype.name.md) - -## IFieldType.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md deleted file mode 100644 index 252c2c38220465..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [script](./kibana-plugin-plugins-data-public.ifieldtype.script.md) - -## IFieldType.script property - -Signature: - -```typescript -script?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md deleted file mode 100644 index 33bbd0c2c20cb8..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [scripted](./kibana-plugin-plugins-data-public.ifieldtype.scripted.md) - -## IFieldType.scripted property - -Signature: - -```typescript -scripted?: boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md deleted file mode 100644 index d0c26186da0857..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md) - -## IFieldType.subType property - -Signature: - -```typescript -subType?: IFieldSubType; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md deleted file mode 100644 index 26228cbe4bfdb1..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md) - -## IFieldType.type property - -Signature: - -```typescript -type: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md new file mode 100644 index 00000000000000..792bee44f96a85 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) + +## IIndexPattern.fields property + +Signature: + +```typescript +fields: IFieldType[]; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md index ec29ef81a6e69b..c4410737811697 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md @@ -20,6 +20,7 @@ export interface IIndexPattern extends IndexPatternBase | Property | Type | Description | | --- | --- | --- | | [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, SerializedFieldFormat<unknown> | undefined> | | +| [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) | IFieldType[] | | | [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat | Look up a formatter for a given field | | [timeFieldName](./kibana-plugin-plugins-data-public.iindexpattern.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md index e42980bb53af48..1bbe0b594ecf01 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md @@ -9,7 +9,9 @@ ```typescript getAggregationRestrictions(): Recordboolean | | | [sortable](./kibana-plugin-plugins-data-public.indexpatternfield.sortable.md) | | boolean | | | [spec](./kibana-plugin-plugins-data-public.indexpatternfield.spec.md) | | FieldSpec | | -| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("../types").IFieldSubType | undefined | | +| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("../..").IFieldSubType | undefined | | | [type](./kibana-plugin-plugins-data-public.indexpatternfield.type.md) | | string | | | [visualizable](./kibana-plugin-plugins-data-public.indexpatternfield.visualizable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md index 5c3c4d54ad0997..6cd5247291602d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md @@ -7,5 +7,5 @@ Signature: ```typescript -get subType(): import("../types").IFieldSubType | undefined; +get subType(): import("../..").IFieldSubType | undefined; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md index 8882fa05ce0c25..b77f3d1f374fbf 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md @@ -19,7 +19,7 @@ toJSON(): { searchable: boolean; aggregatable: boolean; readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; + subType: import("../..").IFieldSubType | undefined; customLabel: string | undefined; }; ``` @@ -37,7 +37,7 @@ toJSON(): { searchable: boolean; aggregatable: boolean; readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; + subType: import("../..").IFieldSubType | undefined; customLabel: string | undefined; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md index d009cad9ec601c..594afcf9ee0ddd 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md @@ -11,11 +11,11 @@ esFilters: { buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; buildCustomFilter: typeof buildCustomFilter; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; + buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; buildFilter: typeof buildFilter; - buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; - buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; + buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; + buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; + buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; isFilterDisabled: (filter: import("../common").Filter) => boolean; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md deleted file mode 100644 index 3d5a757cb8f187..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [lang](./kibana-plugin-plugins-data-server.ifieldtype.lang.md) - -## IFieldType.lang property - -Signature: - -```typescript -lang?: estypes.ScriptLanguage; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md index bbc4cc2135d406..9f14bedf920087 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md @@ -12,7 +12,7 @@ Signature: ```typescript -export interface IFieldType +export interface IFieldType extends IndexPatternFieldBase ``` ## Properties @@ -26,15 +26,9 @@ export interface IFieldType | [esTypes](./kibana-plugin-plugins-data-server.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-server.ifieldtype.filterable.md) | boolean | | | [format](./kibana-plugin-plugins-data-server.ifieldtype.format.md) | any | | -| [lang](./kibana-plugin-plugins-data-server.ifieldtype.lang.md) | estypes.ScriptLanguage | | -| [name](./kibana-plugin-plugins-data-server.ifieldtype.name.md) | string | | | [readFromDocValues](./kibana-plugin-plugins-data-server.ifieldtype.readfromdocvalues.md) | boolean | | -| [script](./kibana-plugin-plugins-data-server.ifieldtype.script.md) | string | | -| [scripted](./kibana-plugin-plugins-data-server.ifieldtype.scripted.md) | boolean | | | [searchable](./kibana-plugin-plugins-data-server.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-server.ifieldtype.sortable.md) | boolean | | -| [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md) | IFieldSubType | | | [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | -| [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-server.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md deleted file mode 100644 index 8be33a3f56d976..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [name](./kibana-plugin-plugins-data-server.ifieldtype.name.md) - -## IFieldType.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md deleted file mode 100644 index b54a952a112531..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [script](./kibana-plugin-plugins-data-server.ifieldtype.script.md) - -## IFieldType.script property - -Signature: - -```typescript -script?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md deleted file mode 100644 index f7a8ed9aee0df4..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [scripted](./kibana-plugin-plugins-data-server.ifieldtype.scripted.md) - -## IFieldType.scripted property - -Signature: - -```typescript -scripted?: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md deleted file mode 100644 index fa78b23a2b558c..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md) - -## IFieldType.subType property - -Signature: - -```typescript -subType?: IFieldSubType; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md deleted file mode 100644 index ef6a4dcc167c53..00000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md) - -## IFieldType.type property - -Signature: - -```typescript -type: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md index b655e779e4fa4d..70a3da86e9fbd3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md @@ -9,7 +9,9 @@ ```typescript getAggregationRestrictions(): Record> file to proxy EMS requests through the Kibana server. -. Update your firewall rules to allow connections from your Kibana server to the EMS domains. - -NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server. - - [float] === Disable Elastic Maps Service diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index a14bda2bf5a986..eb3130ba6fdb5f 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -308,3 +308,12 @@ This content has moved. Refer to <>. This content has moved. Refer to <>. +[role="exclude",id="embedding"] +== Embed {kib} content in a web page + +This content has moved. Refer to <> and <>. + +[role="exclude",id="reporting-troubleshooting-system-dependencies"] +== System dependencies + +This content has moved. Refer to <>. \ No newline at end of file diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 70f3e272fa5a99..b339daf3d36f76 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -4,29 +4,75 @@ ++++ Reporting settings ++++ +:keywords: administrator, reference, setup, reporting +:description: A reference of the reporting settings administrators configure in kibana.yml. You can configure `xpack.reporting` settings in your `kibana.yml` to: +* <> +* <> +* <> * <> * <> * <> +* <> * <> [float] [[general-reporting-settings]] -==== General reporting settings +==== Enable reporting -[cols="2*<"] -|=== -| [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon} - | Set to `false` to disable the {report-features}. +[[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon}:: +When `true`, enables the {report-features}. The {report-features} are automatically enabled in {kib}. The default is `true`. -|[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon} - | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it - starts, which will cause pending reports to fail after restart. Configure this - setting to preserve the same key across multiple restarts and multiple instances of {kib}. +[float] +[[encryption-keys]] +==== Encryption key setting + +By default, an encryption key is generated for the {report-features} each +time you start {kib}. If a static encryption key is not persisted in +the {kib} configuration, any pending reports fail when you restart {kib}. + +If you are load balancing across multiple {kib} instances, each instance needs to have +the same reporting encryption key. Otherwise, report generation fails if a +report is queued through one instance, and another instance picks up the job +from the report queue. The instance that picks up the job is unable to decrypt the +reporting job metadata. + +[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon}:: +The static encryption key for reporting. Use an alphanumeric text string that is at least 32 characters. By default, {kib} generates a random key when it starts, which causes pending reports to fail after restart. Configure `xpack.reporting.encryptionKey` to preserve the same key across multiple restarts and multiple {kib} instances. -|=== +[source,yaml] +-------------------------------------------------------------------------------- +xpack.reporting.encryptionKey: "something_secret" +-------------------------------------------------------------------------------- + +[float] +[[report-indices]] +==== Reporting index setting + + + +`xpack.reporting.index`:: +deprecated:[7.11.0,This setting will be removed in 8.0.0.] Multitenancy by changing `kibana.index` is unsupported starting in 8.0.0. For more details, refer to https://ela.st/kbn-remove-legacy-multitenancy[8.0 Breaking Changes]. When you divide workspaces in an Elastic cluster using multiple {kib} instances with a different `kibana.index` setting per instance, you must set a unique `xpack.reporting.index` setting per `kibana.index`. Otherwise, report generation periodically fails if a report is queued through an instance with one `kibana.index` setting, and an instance with a different `kibana.index` attempts to claim the job. Reporting uses a weekly index in {es} to store the reporting job and the report content. The index is automatically created if it does not already exist. Configure a unique value for `xpack.reporting.index`, beginning with `.reporting-`, for every {kib} instance that has a unique <> setting. Defaults to `.reporting`. + +{kib} instance A: +[source,yaml] +-------------------------------------------------------------------------------- +kibana.index: ".kibana-a" +xpack.reporting.index: ".reporting-a" +xpack.reporting.encryptionKey: "something_secret" +-------------------------------------------------------------------------------- + +{kib} instance B: +[source,yaml] +-------------------------------------------------------------------------------- +kibana.index: ".kibana-b" +xpack.reporting.index: ".reporting-b" +xpack.reporting.encryptionKey: "something_secret" +-------------------------------------------------------------------------------- + +NOTE: If security is enabled, the `xpack.reporting.index` setting should begin with `.reporting-` for the `kibana_system` role to have the necessary privileges over the index. [float] [[reporting-kibana-server-settings]] @@ -34,44 +80,37 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: Reporting opens the {kib} web interface in a server process to generate screenshots of {kib} visualizations. In most cases, the default settings -will work and you don't need to configure Reporting to communicate with {kib}. -However, if your client connections must go through a reverse-proxy -to access {kib}, Reporting configuration must have the proxy port, protocol, -and hostname set in the `xpack.reporting.kibanaServer.*` settings. +work and you don't need to configure the {report-features} to communicate with {kib}. + +If your {kib} instance requires a reverse proxy (such as NGINX, Apache, etc.) for +access, because of rewrite rules or special headers being added by the proxy, +you must configure the `xpack.reporting.kibanaServer` settings to make +the headless browser process connect to the proxy. [NOTE] -==== -If a reverse-proxy carries encrypted traffic from end-user +============ +If a reverse proxy carries encrypted traffic from user clients back to a {kib} server, the proxy port, protocol, and hostname -in Reporting settings must be valid for the encryption that the Reporting -browser will receive. Encrypted communications will fail if there are +in `xpack.reporting.kibanaServer` must be valid for the encryption that the Reporting +browser receives. Encrypted communications fail if there are mismatches in the host information between the request and the certificate on the server. Configuring the `xpack.reporting.kibanaServer` settings to point to a proxy host requires that the {kib} server has network access to the proxy. -==== - -[cols="2*<"] -|=== -| `xpack.reporting.kibanaServer.port` - | The port for accessing {kib}, if different from the <> value. +============ -| `xpack.reporting.kibanaServer.protocol` - | The protocol for accessing {kib}, typically `http` or `https`. +`xpack.reporting.kibanaServer.port`:: The port for accessing {kib}, if different from the <> value. -|[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname` - | The hostname for accessing {kib}, if different from the <> value. +`xpack.reporting.kibanaServer.protocol`:: +The protocol for accessing {kib}, typically `http` or `https`. -|=== +[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname`:: +The hostname for accessing {kib}, if different from the <> value. -[NOTE] -============ -Reporting authenticates requests on the {kib} page only when the hostname matches the -<> setting. Therefore Reporting would fail if the +NOTE: Reporting authenticates requests on the {kib} page only when the hostname matches the +<> setting. Therefore Reporting fails if the set value redirects to another server. For that reason, `"0"` is an invalid setting because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`. -============ - [float] [[reporting-job-queue-settings]] @@ -81,98 +120,50 @@ Reporting generates reports in the background and jobs are coordinated using doc in {es}. Depending on how often you generate reports and the overall number of reports, you might need to change the following settings. -[cols="2*<"] -|=== -| `xpack.reporting.queue.indexInterval` - | How often the index that stores reporting jobs rolls over to a new index. - Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`. - -| `xpack.reporting.queue.pollEnabled` {ess-icon} - | Set to `true` (default) to enable the {kib} instance to poll the index for - pending jobs and claim them for execution. Setting this to `false` allows the - {kib} instance to only add new jobs to the reporting queue, list jobs, and - provide the downloads to completed report through the UI. +`xpack.reporting.queue.indexInterval`:: +How often the index that stores reporting jobs rolls over to a new index. Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`. -|=== +`xpack.reporting.queue.pollEnabled` {ess-icon}:: +Set to `true` (default) to enable the {kib} instance to poll the index for pending jobs and claim them for execution. Setting this to `false` allows the {kib} instance to only add new jobs to the reporting queue, list jobs, and provide the downloads to completed report through the UI. -[NOTE] -============ -Running multiple instances of {kib} in a cluster for load balancing of +NOTE: Running multiple instances of {kib} in a cluster for load balancing of reporting requires identical values for <> and, if security is enabled, <>. -============ -[cols="2*<"] -|=== -| `xpack.reporting.queue.pollInterval` - | Specify the {ref}/common-options.html#time-units[time] that the reporting poller waits between polling the index for any - pending Reporting jobs. Can be specified as number of milliseconds. Defaults to `3s`. +`xpack.reporting.queue.pollInterval`:: +Specifies the {time-units}[time] that the reporting poller waits between polling the index for any pending Reporting jobs. Can be specified as number of milliseconds. Defaults to `3s`. -| [[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon} - | {ref}/common-options.html#time-units[How long] each worker has to produce a report. If your machine is slow or under heavy - load, you might need to increase this timeout. If a Reporting job execution goes over this time limit, the job is marked as a - failure and no download will be available. Can be specified as number of milliseconds. - Defaults to `2m`. - -|=== +[[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon}:: +{time-units}[How long] each worker has to produce a report. If your machine is slow or under heavy load, you might need to increase this timeout. If a Reporting job execution goes over this time limit, the job is marked as a failure and no download will be available. Can be specified as number of milliseconds. Defaults to `2m`. [float] [[reporting-capture-settings]] ==== Capture settings -Reporting works by capturing screenshots from {kib}. The following settings -control the capturing process. - -[cols="2*<"] -|=== -a| `xpack.reporting.capture.timeouts` -`.openUrl` {ess-icon} - | Specify the {ref}/common-options.html#time-units[time] to allow the Reporting browser to wait for the "Loading..." screen - to dismiss and find the initial data for the page. If the time is exceeded, a screenshot is captured showing the current - page, and the download link shows a warning message. Can be specified as number of milliseconds. - Defaults to `1m`. - -a| `xpack.reporting.capture.timeouts` -`.waitForElements` {ess-icon} - | Specify the {ref}/common-options.html#time-units[time] to allow the Reporting browser to wait for all visualization panels - to load on the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows - a warning message. Can be specified as number of milliseconds. - Defaults to `30s`. - -a| `xpack.reporting.capture.timeouts` -`.renderComplete` {ess-icon} - | Specify the {ref}/common-options.html#time-units[time] to allow the Reporting browser to wait for all visualizations to - fetch and render the data. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a - warning message. Can be specified as number of milliseconds. - Defaults to `30s`. - -|=== +Reporting works by capturing screenshots from {kib}. The following settings control the capturing process. -[NOTE] -============ -If any timeouts from `xpack.reporting.capture.timeouts.*` settings occur when +`xpack.reporting.capture.timeouts.openUrl` {ess-icon}:: +Specify the {time-units}[time] to allow the Reporting browser to wait for the "Loading..." screen to dismiss and find the initial data for the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `1m`. + +`xpack.reporting.capture.timeouts.waitForElements` {ess-icon}:: + Specify the {time-units}[time] to allow the Reporting browser to wait for all visualization panels to load on the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `30s`. + +`xpack.reporting.capture.timeouts.renderComplete` {ess-icon}:: + Specify the {time-units}[time] to allow the Reporting browser to wait for all visualizations to fetch and render the data. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `30s`. + +NOTE: If any timeouts from `xpack.reporting.capture.timeouts.*` settings occur when running a report job, Reporting will log the error and try to continue capturing the page with a screenshot. As a result, a download will be available, but there will likely be errors in the visualizations in the report. -============ -[cols="2*<"] -|=== -| `xpack.reporting.capture.maxAttempts` {ess-icon} - | If capturing a report fails for any reason, {kib} will re-attempt other reporting - job, as many times as this setting. Defaults to `3`. +`xpack.reporting.capture.maxAttempts` {ess-icon}:: +If capturing a report fails for any reason, {kib} will re-attempt other reporting job, as many times as this setting. Defaults to `3`. -| `xpack.reporting.capture.loadDelay` - | Specify the {ref}/common-options.html#time-units[amount of time] before taking a screenshot when visualizations are not evented. - All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images - instead of visualizations, try increasing this value. - Defaults to `3s`. +`xpack.reporting.capture.loadDelay`:: +Specify the {time-units}[amount of time] before taking a screenshot when visualizations are not evented. All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images instead of visualizations, try increasing this value. Defaults to `3s`. -| [[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` {ess-icon} - | Specifies the browser to use to capture screenshots. This setting exists for - backward compatibility. The only valid option is `chromium`. - -|=== +[[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` {ess-icon}:: +Specifies the browser to use to capture screenshots. This setting exists for backward compatibility. The only valid option is `chromium`. [float] [[reporting-chromium-settings]] @@ -180,42 +171,85 @@ available, but there will likely be errors in the visualizations in the report. When <> is set to `chromium` (default) you can also specify the following settings. -[cols="2*<"] -|=== -a| `xpack.reporting.capture.browser` -`.chromium.disableSandbox` - | It is recommended that you research the feasibility of enabling unprivileged user namespaces. - See Chromium Sandbox for additional information. Defaults to false for all operating systems except Debian, - Red Hat Linux, and CentOS which use true. +`xpack.reporting.capture.browser.chromium.disableSandbox`:: +It is recommended that you research the feasibility of enabling unprivileged user namespaces. An exception is if you are running {kib} in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters. For more information, refer to <>. Defaults to `false` for all operating systems except Debian, Red Hat Linux, and CentOS, which use `true`. + +`xpack.reporting.capture.browser.chromium.proxy.enabled`:: +Enables the proxy for Chromium to use. When set to `true`, you must also specify the `xpack.reporting.capture.browser.chromium.proxy.server` setting. Defaults to `false`. + +`xpack.reporting.capture.browser.chromium.proxy.server`:: +The uri for the proxy server. Providing the username and password for the proxy server via the uri is not supported. + +`xpack.reporting.capture.browser.chromium.proxy.bypass`:: +An array of hosts that should not go through the proxy server and should use a direct connection instead. Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601". + +[float] +[[reporting-network-policy]] +=== Network policy settings + +To generate PDF reports, *Reporting* uses the Chromium browser to fully load the {kib} page on the server. This potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a field formatted as an image, or to show an image in a Markdown visualization. + +If the Chromium browser is asked to send a request that violates the network policy, *Reporting* stops processing the page before the request goes out, and the report is marked as a failure. Additional information about the event is in the {kib} server logs. + +NOTE: {kib} installations are not designed to be publicly accessible over the internet. The Reporting network policy and other capabilities of the Elastic Stack security features do not change this condition. + +`xpack.reporting.capture.networkPolicy`:: +Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown visualization can show an image from a remote server. + +`xpack.reporting.capture.networkPolicy.enabled`:: +When `false`, disables the *Reporting* network policy. Defaults to `true`. + +`xpack.reporting.capture.networkPolicy.rules`:: +A policy is specified as an array of objects that describe what to allow or deny based on a host or protocol. If a host or protocol is not specified, the rule matches any host or protocol. -a| `xpack.reporting.capture.browser` -`.chromium.proxy.enabled` - | Enables the proxy for Chromium to use. When set to `true`, you must also specify the - `xpack.reporting.capture.browser.chromium.proxy.server` setting. - Defaults to `false`. +The rule objects are evaluated sequentially from the beginning to the end of the array, and continue until there is a matching rule. If no rules allow a request, the request is denied. -a| `xpack.reporting.capture.browser` -`.chromium.proxy.server` - | The uri for the proxy server. Providing the username and password for the proxy server via the uri is not supported. +[source,yaml] +------------------------------------------------------- +# Only allow requests to placeholder.com +xpack.reporting.capture.networkPolicy: + rules: [ { allow: true, host: "placeholder.com" } ] +------------------------------------------------------- -a| `xpack.reporting.capture.browser` -`.chromium.proxy.bypass` - | An array of hosts that should not go through the proxy server and should use a direct connection instead. - Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601". +[source,yaml] +------------------------------------------------------- +# Only allow requests to https://placeholder.com +xpack.reporting.capture.networkPolicy: + rules: [ { allow: true, host: "placeholder.com", protocol: "https:" } ] +------------------------------------------------------- -|=== +A final `allow` rule with no host or protocol allows all requests that are not explicitly denied: + +[source,yaml] +------------------------------------------------------- +# Denies requests from http://placeholder.com, but anything else is allowed. +xpack.reporting.capture.networkPolicy: + rules: [{ allow: false, host: "placeholder.com", protocol: "http:" }, { allow: true }]; +------------------------------------------------------- + +A network policy can be composed of multiple rules: + +[source,yaml] +------------------------------------------------------- +# Allow any request to http://placeholder.com but for any other host, https is required +xpack.reporting.capture.networkPolicy + rules: [ + { allow: true, host: "placeholder.com", protocol: "http:" }, + { allow: true, protocol: "https:" }, + ] +------------------------------------------------------- + +[NOTE] +============ +The `file:` protocol is always denied, even if no network policy is configured. +============ [float] [[reporting-csv-settings]] ==== CSV settings -[cols="2*<"] -|=== -| [[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon} - | The maximum {ref}/common-options.html#byte-units[byte size] of a CSV file before being truncated. This setting exists to - prevent large exports from causing performance and storage issues. Can be specified as number of bytes. - Defaults to `10mb`. -|=== +[[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon}:: +The maximum {byte-units}[byte size] of a CSV file before being truncated. This setting exists to prevent large exports from causing performance and storage issues. Can be specified as number of bytes. Defaults to `10mb`. [NOTE] ============ @@ -230,69 +264,33 @@ on multiple factors: For information about {kib} memory limits, see <>. ============ -[cols="2*<"] -|=== +`xpack.reporting.csv.scroll.size`:: +Number of documents retrieved from {es} for each scroll iteration during a CSV export. Defaults to `500`. -| `xpack.reporting.csv.scroll.size` - | Number of documents retrieved from {es} for each scroll iteration during a CSV - export. - Defaults to `500`. +`xpack.reporting.csv.scroll.duration`:: + Amount of {time-units}[time] allowed before {kib} cleans the scroll context during a CSV export. Defaults to `30s`. -| `xpack.reporting.csv.scroll.duration` - | Amount of {ref}/common-options.html#time-units[time] allowed before {kib} cleans the scroll context during a CSV export. - Defaults to `30s`. +`xpack.reporting.csv.checkForFormulas`:: +Enables a check that warns you when there's a potential formula involved in the output (=, -, +, and @ chars). See OWASP: https://www.owasp.org/index.php/CSV_Injection. Defaults to `true`. -| `xpack.reporting.csv.checkForFormulas` - | Enables a check that warns you when there's a potential formula involved in the output (=, -, +, and @ chars). - See OWASP: https://www.owasp.org/index.php/CSV_Injection - Defaults to `true`. - -| `xpack.reporting.csv` `.enablePanelActionDownload` - | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. - *Note:* This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard - is enabled when Reporting is enabled. - -|=== +`xpack.reporting.csv` `.enablePanelActionDownload`:: +Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search. +NOTE: This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard is enabled when Reporting is enabled. [float] [[reporting-advanced-settings]] -==== Advanced settings - -[cols="2*<"] -|=== -| `xpack.reporting.capture.networkPolicy` - | Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown - visualization can show an image from a remote server. You can configure what type of requests to allow or filter by setting a - <> for Reporting. - -| `xpack.reporting.index` - | deprecated:[7.11.0,This setting will be removed in 8.0.] Multitenancy by - changing `kibana.index` will not be supported starting in 8.0. See - https://ela.st/kbn-remove-legacy-multitenancy[8.0 Breaking Changes] for more - details. Reporting uses a weekly index in {es} to store the reporting job and - the report content. The index is automatically created if it does not already - exist. Configure this to a unique value, beginning with `.reporting-`, for - every {kib} instance that has a unique <> - setting. Defaults to `.reporting`. - -| [[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled` - | deprecated:[7.14.0,This setting must be set to `false` in 8.0.] When `true`, grants users - access to the {report-features} by assigning reporting roles, specified by `xpack.reporting.roles.allow`. - Granting access to users this way is deprecated. Set to `false` and use - {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. - Defaults to `true`. - -| `xpack.reporting.roles.allow` - | deprecated:[7.14.0,This setting will be removed in 8.0.] Specifies the roles, - in addition to superusers, that can generate reports, using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. - Requires `xpack.reporting.roles.enabled` to be `true`. - Granting access to users this way is deprecated. Use - {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. - Defaults to `[ "reporting_user" ]`. - -|=== +==== Security settings + +[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`:: +deprecated:[7.14.0,This setting must be set to `false` in 8.0.] When `true`, grants users access to the {report-features} by assigning reporting roles, specified by `xpack.reporting.roles.allow`. Granting access to users this way is deprecated. Set to `false` and use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `true`. [NOTE] -============ -Each user has access to only their own reports. -============ +============================================================================ +In 7.x, the default value of `xpack.reporting.roles.enabled` is `true`. To migrate users to the +new method of securing access to *Reporting*, you must set `xpack.reporting.roles.enabled: false`. In the next major version of {kib}, `false` will be the only valid configuration. +============================================================================ + +`xpack.reporting.roles.allow`:: +deprecated:[7.14.0,This setting will be removed in 8.0.] Specifies the roles, in addition to superusers, that can generate reports, using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` to be `true`. Granting access to users this way is deprecated. Use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `[ "reporting_user" ]`. + +NOTE: Each user has access to only their own reports. diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc new file mode 100644 index 00000000000000..af4fc14448ac57 --- /dev/null +++ b/docs/setup/configuring-reporting.asciidoc @@ -0,0 +1,235 @@ +[role="xpack"] +[[secure-reporting]] +== Configure reporting in {kib} + +++++ +Configure reporting +++++ + +To enable users to manually and automatically generate reports, install the reporting packages, grant users access to the {report-features}, and secure the reporting endpoints. + +[float] +[[install-reporting-packages]] +=== Install the reporting packages + +Make sure the {kib} server operating system has the appropriate packages installed for the distribution. + +If you are using CentOS/RHEL systems, install the following packages: + +* `ipa-gothic-fonts` +* `xorg-x11-fonts-100dpi` +* `xorg-x11-fonts-75dpi` +* `xorg-x11-utils` +* `xorg-x11-fonts-cyrillic` +* `xorg-x11-fonts-Type1` +* `xorg-x11-fonts-misc` +* `fontconfig` +* `freetype` + +If you are using Ubuntu/Debian systems, install the following packages: + +* `fonts-liberation` +* `libfontconfig1` + +If the system is missing dependencies, *Reporting* fails in a non-deterministic way. {kib} runs a self-test at server startup, and +if it encounters errors, logs them in the Console. The error message does not include +information about why Chromium failed to run. The most common error message is `Error: connect ECONNREFUSED`, which indicates +that {kib} could not connect to the Chromium process. + +To troubleshoot the problem, start the {kib} server with environment variables that tell Chromium to print verbose logs. For more information, refer to <>. + +[float] +[[grant-user-access]] +=== Grant users access to reporting + +When security is enabled, access to the {report-features} is controlled by roles and privileges. + +[[reporting-app-users]] +In 7.12.0 and earlier, you grant access to the {report-features} by assigning users the `reporting_user` role in {es}. + +In 7.14.0 and later, you configure *Reporting* to use <>. By using {kib} privileges, you can define custom roles that grant *Reporting* privileges as sub-features of {kib} applications. + +To grant users permission to generate reports and view their reports in *Reporting*, create and assign the reporting role. + +. Create the reporting role. + +.. Open the main menu, then click *Stack Management*. + +.. Click *Roles > Create role*. + +. Specify the role settings. + +.. Enter the *Role name*. For example, `custom_reporting_user`. + +.. Specify the *Indices* and *Privileges*. ++ +Access to data is an index-level privilege. For each index that contains the data you want to include in reports, add a line, then give each index `read` and `view_index_metadata` privileges. ++ +For more information, refer to {ref}/security-privileges.html[Security privileges]. ++ +[role="screenshot"] +image::user/security/images/reporting-privileges-example.png["Reporting privileges"] + +. Add the {kib} privileges. + +.. Click *Add Kibana privilege*. + +.. Select one or more *Spaces* that you want to grant reporting privileges to. + +.. Click *Customize*, then click *Analytics*. + +.. Next to each application you want to grant reporting privileges to, click *All*. ++ +[role="screenshot"] +image::user/security/images/reporting-custom-role.png["Reporting custom role"] + +.. Click *Add {kib} privilege*. + +. Click *Create role*. + +. Assign the reporting role to a user. + +.. Open the main menu, then click *Stack Management*. + +.. Click *Users*, then click the user you want to assign the reporting role to. + +.. From the *Roles* dropdown, select *custom_reporting_user*. + +.. Click *Update user*. + +[float] +[[reporting-roles-user-api]] +==== Grant access with the role API +You can also use the {ref}/security-api-put-role.html[role API] to grant access to the reporting features. Grant the reporting role to users in combination with other roles that grant read access to the data in {es}, and at least read access in the applications where users can generate reports. + +[source, sh] +--------------------------------------------------------------- +POST /_security/role/custom_reporting_user +{ + metadata: {}, + elasticsearch: { cluster: [], indices: [], run_as: [] }, + kibana: [ + { + base: [], + feature: { + dashboard: [ + 'generate_report', <1> + 'download_csv_report' <2> + ], + discover: ['generate_report'], <3> + canvas: ['generate_report'], <4> + visualize: ['generate_report'], <5> + }, + spaces: ['*'], + } + ] +} +--------------------------------------------------------------- +// CONSOLE + +<1> Grants access to generate PNG and PDF reports in *Dashboard*. +<2> Grants access to download CSV files from saved search panels in *Dashboard*. +<3> Grants access to generate CSV reports from saved searches in *Discover*. +<4> Grants access to generate PDF reports in *Canvas*. +<5> Grants access to generate PNG and PDF reports in *Visualize Library*. + +[float] +==== Grant access using an external provider + +If you are using an external identity provider, such as LDAP or Active Directory, you can assign roles to individual users or groups of users. Role mappings are configured in {ref}/mapping-roles.html[`config/role_mapping.yml`]. + +For example, assign the `kibana_admin` and `reporting_user` roles to the Bill Murray user: + +[source,yaml] +-------------------------------------------------------------------------------- +kibana_admin: + - "cn=Bill Murray,dc=example,dc=com" +reporting_user: + - "cn=Bill Murray,dc=example,dc=com" +-------------------------------------------------------------------------------- + +[float] +==== Grant access with a custom index + +If you are using a custom index, the `xpack.reporting.index` setting must begin with `.reporting-*`. The default {kib} system user has `all` privileges against the `.reporting-*` pattern of indices. + +If you use a different pattern for the `xpack.reporting.index` setting, you must create a custom `kibana_system` user with appropriate access to the index. + +NOTE: In the next major version of Kibana, granting access with a custom index is unsupported. + +. Create the reporting role. + +.. Open the main menu, then click *Stack Management*. + +.. Click *Roles > Create role*. + +. Specify the role settings. + +.. Enter the *Role name*. For example, `custom-reporting-user`. + +.. From the *Indices* dropdown, select the custom index. + +.. From the *Privileges* dropdown, select *all*. + +.. Click *Add Kibana privilege*. + +.. Select one or more *Spaces* that you want to grant reporting privileges to. + +.. Click *Customize*, then click *Analytics*. + +.. Next to each application you want to grant reporting privileges to, click *All*. + +.. Click *Add {kib} privilege*, then click *Create role*. + +. Assign the reporting role to a user. + +.. Open the main menu, then click *Stack Management*. + +.. Click *Users*, then click the user you want to assign the reporting role to. + +.. From the *Roles* dropdown, select *kibana_system* and *custom-reporting-user*. + +.. Click *Update user*. + +. Configure {kib} to use the new account. ++ +[source,js] +-------------------------------------------------------------------------------- +elasticsearch.username: 'custom_kibana_system' +-------------------------------------------------------------------------------- + +[float] +[[securing-reporting]] +=== Secure the reporting endpoints + +To automatically generate reports with {watcher}, you must configure {watcher} to trust the {kib} server certificate. + +. Enable {stack-security-features} on your {es} cluster. For more information, see {ref}/security-getting-started.html[Getting started with security]. + +. Configure TLS/SSL encryption for the {kib} server. For more information, see <>. + +. Specify the {kib} server CA certificate chain in `elasticsearch.yml`: ++ +-- +If you are using your own CA to sign the {kib} server certificate, then you need to specify the CA certificate chain in {es} to properly establish trust in TLS connections between {watcher} and {kib}. If your CA certificate chain is contained in a PKCS #12 trust store, specify it like so: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.http.ssl.truststore.path: "/path/to/your/truststore.p12" +xpack.http.ssl.truststore.type: "PKCS12" +xpack.http.ssl.truststore.password: "optional decryption password" +-------------------------------------------------------------------------------- + +Otherwise, if your CA certificate chain is in PEM format, specify it like so: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.http.ssl.certificate_authorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] +-------------------------------------------------------------------------------- + +For more information, see {ref}/notification-settings.html#ssl-notification-settings[the {watcher} HTTP TLS/SSL Settings]. +-- + +. Add one or more users who have access to the {report-features}. ++ +Once you've enabled SSL for {kib}, all requests to the reporting endpoints must include valid credentials. diff --git a/docs/setup/embedding.asciidoc b/docs/setup/embedding.asciidoc deleted file mode 100644 index 0c48aeefc95577..00000000000000 --- a/docs/setup/embedding.asciidoc +++ /dev/null @@ -1,63 +0,0 @@ -[[embedding]] -== Embed {kib} content in a web page - -Once you create a dashboard or a visualization, you might want to share it with your colleagues or friends. The easiest way to do this is to share a direct link to your dashboard or visualization. However, some users might not have access to your {kib}. - -With the {kib} embedding functionality, you can display the content you created in {kib} to an internal company website or a personal web page. - -. Open the main menu, then click *Dashboard* or *Visualize Library*. - -. Open the dashboard or visualization you want to embed. - -. To generate the HTML code snippet, open the *Share* menu, then click *Embed code > Copy iFrame code*. -+ -You can embed this snippet in your web page, and then add analysis, images, and links to give more context to the object you're sharing. -+ -image::images/embed-kibana.png[Generate an HTML snippet to embed {kib}, align=center] -+ -NOTE: Embedding of any other part of {kib} is also generally possible, but you might need to craft the proper HTML code manually. - -[float] -[[embedding-security]] -=== Configure security - -Embedding content through iframes requires careful consideration to minimize security risks. By default, modern web browsers enforce the -https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy[same-origin policy] to restrict the behavior of framed pages. When -{stack-security-features} are enabled on your cluster, you must relax this constraint for cookies as described in <> for {kib} to function -in an iframe. Refer to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe[iframe] and -https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite[SameSite cookies] for more information. - -[float] -==== Authentication -If you're embedding {kib} in a website that supports Single Sign-On with SAML, OpenID Connect, Kerberos, or PKI, it's highly advisable to configure {kib} as a part of the Single Sign-On setup. Operating in a single and properly configured security domain provides you with the most secure and seamless user experience. You can read more at <>. - -If you want users to access embedded {kib} by skipping the login step, and Single Sign-On isn't an option for you, consider configuring <>. It is already natively integrated into the workflow for embedding dashboards and visualizations. - -If you have multiple authentication providers enabled, and you want to automatically log in anonymous users when embedding anything other than dashboards and visualizations, then you will need to add the `auth_provider_hint=` query string parameter to the {kib} URL that you're embedding. - -For example, if you craft the iframe code to embed {kib}, it might look like this: - -```html - -``` - -To make this iframe leverage anonymous access automatically, you will need to modify a link to {kib} in the `src` iframe attribute to look like this: - -```html - -``` - -Note that the `auth_provider_hint` query string parameter goes *before* the hash URL fragment. - -[float] -[[embedding-cookies]] -==== Cookies - -Regardless of the authentication type that you're using for the embedded {kib}, you must make sure that the browsers can transmit session cookies to a {kib} server. The setting you need to be aware of is <>. To support modern browsers, you must set it to `None`: - -[source,yaml] --- -xpack.security.sameSiteCookies: "None" --- - -For more information about possible values and implications, go to <>. \ No newline at end of file diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index a3f815e5ea5044..d9a48835553cfe 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -374,7 +374,8 @@ When `includeElasticMapsService` is turned off, only tile layer configured by << | Specifies the URL of a self hosted <> | `map.proxyElasticMapsServiceInMaps:` - | Set to `true` to proxy all <> Elastic Maps Service + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Set to `true` to proxy all <> Elastic Maps Service requests through the {kib} server. *Default: `false`* | [[regionmap-settings]] `map.regionmap:` {ess-icon} diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index 92cd6e9ead5a1e..bd93517a7a82f5 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -1,8 +1,54 @@ [[upgrade]] == Upgrade {kib} -Depending on the {kib} version you're upgrading from, the upgrade process to 7.0 -varies. +Depending on the {kib} version you're upgrading from, the upgrade process to {version} +varies. The following upgrades are supported: + +* Between minor versions +* From 5.6 to 6.8 +* From 6.8 to {prev-major-version} +* From {prev-major-version} to {version} +ifeval::[ "{version}" != "{minor-version}.0" ] +* From any version since {minor-version}.0 to {version} +endif::[] + +The following table shows the recommended upgrade paths to {version}. + +[cols="<1,3",options="header",] +|==== +|Upgrade from +|Recommended upgrade path to {version} + +ifeval::[ "{version}" != "{minor-version}.0" ] +|A previous {minor-version} version (e.g., {minor-version}.0) +|Upgrade to {version} +endif::[] + +|{prev-major-version} +|Upgrade to {version} + +|7.0–7.7 +a| +. Upgrade to {prev-major-version} +. Upgrade to {version} + +|6.8 +a| +. Upgrade to {prev-major-version} +. Upgrade to {version} + +|6.0–6.7 +a| + +. Upgrade to 6.8 +. Upgrade to {prev-major-version} +. Upgrade to {version} +|==== + +[WARNING] +==== +The upgrade path from 6.8 to 7.0 is *not* supported. +==== [float] [[upgrade-before-you-begin]] diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index b699c56ebd9445..0b4104ec1f31b9 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -24,13 +24,16 @@ This section describes all of these elements and how they operate together. [float] === Rules -A rule specifies a background task that runs on the {kib} server to check for specific conditions. It consists of three main parts: +A rule specifies a background task that runs on the {kib} server to check for specific conditions. {kib} provides two types of rules: stack rules that are built into {kib} and domain rules that are registered by Kibana apps. Refer to <> for more information. + +A rule consists of three main parts: * *Conditions*: what needs to be detected? * *Schedule*: when/how often should detection checks run? * *Actions*: what happens when a condition is detected? -For example, when monitoring a set of servers, a rule might: +For example, when monitoring a set of servers, a rule might: + * Check for average CPU usage > 0.9 on each server for the last two minutes (condition). * Check every minute (schedule). * Send a warning email message via SMTP with subject `CPU on {{server}} is high` (action). @@ -136,4 +139,4 @@ Functionally, {kib} alerting differs in that: At a higher level, {kib} alerting allows rich integrations across use cases like <>, <>, <>, and <>. Pre-packaged *rule types* simplify setup and hide the details of complex, domain-specific detections, while providing a consistent interface across {kib}. --- \ No newline at end of file +-- diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index cc91ebcd99be2d..2dd9a412051216 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -152,6 +152,17 @@ You can perform these operations in bulk by multi-selecting rules, and then clic [role="screenshot"] image:images/bulk-mute-disable.png[The Manage rules button lets you mute/unmute, enable/disable, and delete in bulk,width=75%] +[float] +=== Rule status + +A rule can have one of the following statuses: + +`active`:: The conditions for the rule have been met, and the associated actions should be invoked. +`ok`:: The conditions for the rule were previously met, but no longer. Changed to `recovered` in the 7.14 release. +`error`:: An error was encountered during rule execution. +`pending`:: The rule has not yet executed. The rule was either just created, or enabled after being disabled. +`unknown`:: A problem occurred when calculating the status. Most likely, something went wrong with the alerting code. + [float] [[importing-and-exporting-rules]] === Import and export rules diff --git a/docs/user/alerting/troubleshooting/testing-connectors.asciidoc b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc index c99ac243f0ad3f..f90a7ebc356146 100644 --- a/docs/user/alerting/troubleshooting/testing-connectors.asciidoc +++ b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc @@ -19,7 +19,7 @@ image::user/alerting/images/teams-connector-test.png[Five clauses define the con Executing an Email action via https://github.com/pmuellr/kbn-action[kbn-action]. In this example, is using a cloud deployment of the stack: -[source] +[source, txt] -------------------------------------------------- $ npm -g install pmuellr/kbn-action @@ -45,7 +45,7 @@ $ kbn-action ls -------------------------------------------------- and then execute this: -[source] +[source, txt] -------------------------------------------------- $ kbn-action execute a692dc89-15b9-4a3c-9e47-9fb6872e49ce '{subject: "hallo", message: "hallo!", to:["test@yahoo.com"]}' { diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 1face015f1f76b..08462e0f0ed241 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -194,14 +194,24 @@ Organize and separate your ideas by adding more pages. [role="screenshot"] image::images/canvas-add-pages.gif[Add pages] +[float] +[[workpad-share-options]] +== Share your workpad + +To share workpads with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>. + +[float] +[[export-single-workpad]] +== Export workpads + +Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*. + -- include::{kib-repo-dir}/canvas/canvas-edit-workpads.asciidoc[] include::{kib-repo-dir}/canvas/canvas-present-workpad.asciidoc[] -include::{kib-repo-dir}/canvas/canvas-share-workpad.asciidoc[] - include::{kib-repo-dir}/canvas/canvas-tutorial.asciidoc[] include::{kib-repo-dir}/canvas/canvas-expression-lifecycle.asciidoc[] diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 8226e9c6ed073f..af67b3016454b0 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -301,7 +301,7 @@ image:images/Dashboard_inspect.png[Inspect in dashboard] [[share-the-dashboard]] == Share dashboards -To share the dashboard with a larger audience, click *Share* in the toolbar. For detailed information, refer to <>. +To share the dashboard with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>. [float] [[import-dashboards]] diff --git a/docs/user/dashboard/images/tsvb_index_pattern_selection_mode.png b/docs/user/dashboard/images/tsvb_index_pattern_selection_mode.png new file mode 100644 index 00000000000000..ef72f291850e48 Binary files /dev/null and b/docs/user/dashboard/images/tsvb_index_pattern_selection_mode.png differ diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 93ee3627bd8a0c..9320b062a8ba93 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -30,6 +30,29 @@ By default, *TSVB* drops the last bucket because the time filter intersects the .. In the *Panel filter* field, enter <> to view specific documents. +[float] +[[tsvb-index-pattern-mode]] +==== Index pattern mode +Create *TSVB* visualizations with {kib} index patterns. + +IMPORTANT: Creating *TSVB* visualizations with an {es} index string is deprecated and will be removed in a future release. +It is the default one for new visualizations but it can also be switched for the old implementations: + +. Click *Panel options*, then click the gear icon to open the *Index pattern selection mode* options. +. Select *Use only Kibana index patterns*. +. Reselect the index pattern from the dropdown, then select the *Time field*. + +image::images/tsvb_index_pattern_selection_mode.png[Change index pattern selection mode action] + +The index pattern mode unlocks many new features, such as: +* Runtime fields + +* URL drilldowns + +* Interactive filters for time series visualizations + +* Better performance + [float] [[configure-the-data-series]] ==== Configure the series @@ -177,4 +200,4 @@ To group with multiple fields, create runtime fields in the index pattern you ar [role="screenshot"] image::images/tsvb_group_by_multiple_fields.png[Group by multiple fields] -. Create a new TSVB visualization and group by this field. \ No newline at end of file +. Create a new TSVB visualization and group by this field. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 82a7dd300f028b..f0029f8925b3c4 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -272,6 +272,12 @@ your data appears in a map. [role="screenshot"] image:images/discover-maps.png[Map containing documents] +[float] +[[share-your-findings]] +=== Share your findings + +To share your findings with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>. + [float] === What’s next? diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index 47d86004fdc664..29dd5e49a668b3 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -8,13 +8,6 @@ include::{kib-repo-dir}/getting-started/quick-start-guide.asciidoc[] include::setup.asciidoc[] -include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1] -include::monitoring/monitoring-metricbeat.asciidoc[leveloffset=+2] -include::monitoring/viewing-metrics.asciidoc[leveloffset=+2] -include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2] - -include::security/securing-kibana.asciidoc[] - include::production-considerations/index.asciidoc[] include::discover.asciidoc[] @@ -25,6 +18,8 @@ include::canvas.asciidoc[] include::{kib-repo-dir}/maps/index.asciidoc[] +include::reporting/index.asciidoc[] + include::ml/index.asciidoc[] include::graph/index.asciidoc[] @@ -45,8 +40,6 @@ include::management.asciidoc[] include::{kib-repo-dir}/fleet/fleet.asciidoc[] -include::reporting/index.asciidoc[] - include::api.asciidoc[] include::plugins.asciidoc[] diff --git a/docs/user/production-considerations/index.asciidoc b/docs/user/production-considerations/index.asciidoc index c52aade575968f..198e8324af3e6c 100644 --- a/docs/user/production-considerations/index.asciidoc +++ b/docs/user/production-considerations/index.asciidoc @@ -1,5 +1,6 @@ include::production.asciidoc[] include::alerting-production-considerations.asciidoc[] +include::reporting-production-considerations.asciidoc[] include::task-manager-production-considerations.asciidoc[] include::task-manager-health-monitoring.asciidoc[] include::task-manager-troubleshooting.asciidoc[] diff --git a/docs/user/production-considerations/reporting-production-considerations.asciidoc b/docs/user/production-considerations/reporting-production-considerations.asciidoc new file mode 100644 index 00000000000000..32752cbe69ab8c --- /dev/null +++ b/docs/user/production-considerations/reporting-production-considerations.asciidoc @@ -0,0 +1,36 @@ +[role="xpack"] +[[reporting-production-considerations]] +== Reporting production considerations + +++++ +Reporting +++++ +:keywords: administrator, analyst, concept, setup, reporting +:description: Consider the production components that are used to generate reports. + +To generate reports, {kib} uses a custom build of the Chromium web browser, which runs on the {kib} server in headless mode to load {kib} and capture the rendered {kib} visualizations as images. Chromium is an open-source project not related to Elastic, but the Chromium binary for {kib} has been custom-built by Elastic to make sure it works with minimal setup. The operating system that the {kib} server uses can require additional dependencies for Chromium. + +[float] +[[reporting-chromium-sandbox]] +=== Chromium sandbox +For an additional layer of security, use the sandbox. The Chromium sandbox uses operating system-provided mechanisms to ensure that code execution cannot make persistent changes to the computer or access confidential information. The specific sandboxing techniques differ for each operating system. + +[float] +[[reporting-linux-sandbox]] +==== Linux sandbox +The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many +distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. The {report-features} +automatically disable the sandbox when it is running on Debian and CentOS, as additional steps are required to enable +unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs: +`Chromium sandbox provides an additional layer of protection, but is not supported for your OS. +Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` + +Reporting automatically enables the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's +recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces. + +[float] +[[reporting-docker-sandbox]] +==== Docker +When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and +AppArmor profiles that prevent the Chromium sandbox from being used. In these situations, disabling the sandbox is recommended, +as the container implements similar security mechanisms. \ No newline at end of file diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index 6c86da8e214c16..3ddb4a36949940 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -1,69 +1,62 @@ [role="xpack"] [[automating-report-generation]] -== Automating report generation -Automatically generate PDF and CSV reports by submitting HTTP `POST` requests using {watcher} or a script. +== Automatically generate reports -include::report-intervals.asciidoc[] +To automatically generate PDF and CSV reports, generate a POST URL, then submit the HTTP `POST` request using {watcher} or a script. [float] +[[create-a-post-url]] === Create a POST URL Create the POST URL that triggers a report to generate PDF and CSV reports. To create the POST URL for PDF reports: -. Open the  dashboard, visualization, or **Canvas** workpad. +. Open the main menu, then click *Dashboard, *Visualize Library*, or *Canvas*. -. From the {kib} toolbar, click *Share*, then select *PDF Reports*. +. Open the dashboard, visualization, or **Canvas** workpad you want to view as a report. -. If you are using **Canvas**, click *Advanced options*. +. From the toolbar, click *Share > PDF Reports*, then choose an option: -. Click *Copy POST URL*. -+ -[role="screenshot"] -image::images/report-automate-pdf.png[Automatically generate *Dashboard* and *Visualize Library* reports] +* If you are using *Dashboard* or *Visulize Library*, click *Copy POST URL*. +* If you are using *Canvas*, click *Advanced options > Copy POST URL*. To create the POST URL for CSV reports: -. In *Discover*, open the saved search. +. Open the main menu, then click *Discover*. -. From the {kib} toolbar, click *Share*, then select *CSV Reports*. +. Open the saved search you want to share. -. Click *Copy POST URL*. -+ -[role="screenshot"] -image::images/report-automate-csv.png[Generate Discover reports] +. In the toolbar, click *Share > CSV Reports > Copy POST URL*. [float] +[[use-watcher]] === Use Watcher include::watch-example.asciidoc[] [float] +[[use-a-script]] === Use a script include::script-example.asciidoc[] [float] +[[reporting-response-codes]] === HTTP response codes include::response-codes.asciidoc[] [float] +[[deprecated-report-urls]] === Deprecated report URLs -The following POST URL paths are deprecated. If there are -any problems with using these paths after you upgrade {kib}, use -{kib} to regenerate the POST URL for a particular report. +If you experience issues with the deprecated report URLs after you upgrade {kib}, regenerate the POST URL for your reports. -* Dashboard reports: `/api/reporting/generate/dashboard/` -* Visualize reports: `/api/reporting/generate/visualization/` -* Saved Search reports: `/api/reporting/generate/search/` +* *Dashboard* reports: `/api/reporting/generate/dashboard/` +* *Visualize Library* reports: `/api/reporting/generate/visualization/` +* *Discover* saved search reports: `/api/reporting/generate/search/` -[IMPORTANT] -=================== -Previously there was a `&sync` parameter appended to generation URLs which would hold -the request open until the document was fully generated. This feature has been removed. -Existing use of the `&sync` parameter, in Watcher for example, will need to be updated. -=================== +IMPORTANT: +In earlier {kib} versions, you could use the `&sync` parameter to append to report URLs that held the request open until the document was fully generated. The `&sync` parameter is now unsupported. If you use the `&sync` parameter in Watcher, you must update the parameter. diff --git a/docs/user/reporting/chromium-sandbox.asciidoc b/docs/user/reporting/chromium-sandbox.asciidoc deleted file mode 100644 index dcb421261c0678..00000000000000 --- a/docs/user/reporting/chromium-sandbox.asciidoc +++ /dev/null @@ -1,26 +0,0 @@ -[role="xpack"] -[[reporting-chromium-sandbox]] -=== Chromium sandbox - -When {report-features} uses the Chromium browser for generating PDF reports, -it's recommended to use the sandbox for an additional layer of security. The -Chromium sandbox uses operating system provided mechanisms to ensure that -code execution cannot make persistent changes to the computer or access -confidential information. The specific sandboxing techniques differ for each -operating system. - -==== Linux sandbox -The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many -distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. The {report-features} -will automatically disable the sandbox when it is running on Debian and CentOS as additional steps are required to enable -unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs: -`Chromium sandbox provides an additional layer of protection, but is not supported for your OS. -Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` - -Reporting will automatically enable the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's -recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces. - -==== Docker -When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and -AppArmor profiles that prevent the Chromium sandbox from being used. In these situations, disabling the sandbox is recommended, -as the container implements similar security mechanisms. \ No newline at end of file diff --git a/docs/user/reporting/configuring-reporting.asciidoc b/docs/user/reporting/configuring-reporting.asciidoc deleted file mode 100644 index a8b76f36b9a844..00000000000000 --- a/docs/user/reporting/configuring-reporting.asciidoc +++ /dev/null @@ -1,78 +0,0 @@ -[role="xpack"] -[[configuring-reporting]] -== Reporting configuration - -You can configure settings in `kibana.yml` to control how the {report-features} -communicate with the {kib} server, manages background jobs, and captures -screenshots. See <> for the complete -list of settings. - -[float] -[[encryption-keys]] -=== Encryption keys for multiple {kib} instances - -By default, a new encryption key is generated for the {report-features} each -time you start {kib}. This means if a static encryption key is not persisted in -the {kib} configuration, any pending reports will fail when you restart {kib}. - -If you are load balancing across multiple {kib} instances, they need to have -the same reporting encryption key. Otherwise, report generation will fail if a -report is queued through one instance and another instance picks up the job -from the report queue. The other instance will not be able to decrypt the -reporting job metadata. - -To set a static encryption key for reporting, set the -`xpack.reporting.encryptionKey` property in the `kibana.yml` -configuration file. You can use any alphanumeric, at least 32 characters long text string as the encryption key. - -[source,yaml] --------------------------------------------------------------------------------- -xpack.reporting.encryptionKey: "something_secret" --------------------------------------------------------------------------------- - -[float] -[[report-indices]] -=== Report indices for multiple {kib} workspaces - -If you divide workspaces in an Elastic cluster using multiple {kib} instances -with a different `kibana.index` setting per instance, you must set a unique `xpack.reporting.index` -setting per `kibana.index`. Otherwise, report generation will periodically fail -if a report is queued through an instance with one `kibana.index` setting, and -an instance with a different `kibana.index` attempts to claim the job. - -Kibana instance A: -[source,yaml] --------------------------------------------------------------------------------- -kibana.index: ".kibana-a" -xpack.reporting.index: ".reporting-a" -xpack.reporting.encryptionKey: "something_secret" --------------------------------------------------------------------------------- - -Kibana instance B: -[source,yaml] --------------------------------------------------------------------------------- -kibana.index: ".kibana-b" -xpack.reporting.index: ".reporting-b" -xpack.reporting.encryptionKey: "something_secret" --------------------------------------------------------------------------------- - -NOTE: If security is enabled, the `xpack.reporting.index` setting should begin -with `.reporting-` in order for the `kibana_system` role to have the necessary -privileges over the index. - -[float] -[[using-reverse-proxies]] -=== Use reverse proxies - -If your {kib} instance requires a reverse proxy (NGINX, Apache, etc.) for -access, because of rewrite rules or special headers being added by the proxy, -then you need to configure the `xpack.reporting.kibanaServer` settings to make -the headless browser process connect to the proxy in <>. - -NOTE: A headless browser runs on the Kibana server to open a Kibana page for -capturing screenshots. Configuring the `xpack.reporting.kibanaServer` settings -to point to a proxy host requires that the Kibana server has network access to -the proxy. - -include::{kib-repo-dir}/user/security/reporting.asciidoc[] -include::network-policy.asciidoc[] diff --git a/docs/user/reporting/generating-reports.asciidoc b/docs/user/reporting/generating-reports.asciidoc deleted file mode 100644 index 6503838cb44143..00000000000000 --- a/docs/user/reporting/generating-reports.asciidoc +++ /dev/null @@ -1 +0,0 @@ -[role="xpack"] diff --git a/docs/user/reporting/gs-index.asciidoc b/docs/user/reporting/gs-index.asciidoc deleted file mode 100644 index 46c1fd38b7d69b..00000000000000 --- a/docs/user/reporting/gs-index.asciidoc +++ /dev/null @@ -1,28 +0,0 @@ -[role="xpack"] -[[xpack-reporting]] -= Reporting from Kibana - -[partintro] --- -You can generate reports that contain {kib} dashboards, -visualizations, and saved searches. The reports are exported as -print-optimized PDF documents. - -NOTE: On Linux, the `libfontconfig` and `libfreetype6` packages and system -fonts are required to generate reports. If no system fonts are available, -labels are not rendered correctly in the reports. - -The following Reporting button appears in the {kib} toolbar: - -image:images/reporting.jpg["Reporting",link="reporting.jpg"] - -You can also <>. - -IMPORTANT: Reports are stored in the `.reporting-*` indices. Any user with -access to these indices has access to every report generated by all users. - -To use {report-features} in a production environment, -<>. --- - -include::getting-started.asciidoc[] diff --git a/docs/user/reporting/images/embed-code-public-url.png b/docs/user/reporting/images/embed-code-public-url.png new file mode 100644 index 00000000000000..4ecd6c47122d10 Binary files /dev/null and b/docs/user/reporting/images/embed-code-public-url.png differ diff --git a/docs/user/reporting/images/permalink-public-url.png b/docs/user/reporting/images/permalink-public-url.png new file mode 100644 index 00000000000000..93e7482a16c2ba Binary files /dev/null and b/docs/user/reporting/images/permalink-public-url.png differ diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 9e4271b3659bce..457be5b038c12c 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -1,125 +1,166 @@ [role="xpack"] [[reporting-getting-started]] -= Reporting += Reporting and sharing [partintro] -- -You can generate a report that contains a {kib} dashboard, visualization, -saved search, or Canvas workpad. Depending on the object type, you can export the data as -a PDF, PNG, or CSV document, which you can keep for yourself, or share with others. +:keywords: analyst, concept, task, reporting +:description: {kib} provides you with several options to share *Discover* saved searches, dashboards, *Visualize Library* visualizations, and *Canvas* workpads with others, or on a website. -Reporting is available from the *Share* menu -in *Discover*, *Dashboard*, *Visualize Library*, and *Canvas*. +{kib} provides you with several options to share *Discover* saved searches, dashboards, *Visualize Library* visualizations, and *Canvas* workpads. -[role="screenshot"] -image::user/reporting/images/share-menu.png["Share"] +You access the options from the *Share* menu in the toolbar. The sharing options include the following: -[float] -== Setup +* *PDF Reports* — Generate and download a PDF file of a dashboard, visualization, or *Canvas* workpad. -The {report-features} are automatically enabled in {kib}. It runs a custom build of the Chromium web browser, which -runs on the server in headless mode to load {kib} and capture the rendered {kib} charts as images. +* *PNG Reports* — Generate and download a PNG file of a dashboard or visualization. -Chromium is an open-source project not related to Elastic, but the Chromium binary for {kib} has been custom-built by Elastic to ensure it -works with minimal setup. However, the {kib} server OS might still require additional dependencies for Chromium. See the -<> section for more information about the system dependencies -for different operating systems. +* *CSV Reports* — Generate and download a CSV file of a saved search. -[float] -[[reporting-required-privileges]] -== Roles and privileges +* *Permalinks* — Share a direct link to a *Discover* saved search, dashboard, or visualization. + +* *Download as JSON* — Generate and download a JSON file of a *Canvas* workpad. -When security is enabled, access to the {report-features} is controlled by security privileges. In versions 7.12 and earlier, you can grant access to the {report-features} -by assigning users the `reporting_user` role in {es}. In 7.14 and later, you can configure *Reporting* to use -<>. It is recommended that *Reporting* is configured to -use {kib} privileges by setting <> to `false`. By using {kib} privileges, you can define -custom roles that grant *Reporting* privileges as sub-features of {kib} applications in *Role Management*. +* beta[] *Share on a website* — Download and securely share *Canvas* workpads on any website. -Users must also have the {kib} privileges to access the saved objects and associated {es} indices included in the generated reports. -For an example, refer to <>. +* *Embed code* — Embed a fully interactive dashboard or visualization as an iframe on a web page. [float] [[manually-generate-reports]] -== Manually generate and download reports +== Create reports + +Create and download PDF, PNG, or CSV reports of saved searches, dashboards, visualizations, and workpads. + +[[reporting-layout-sizing]] +The layout and size of the report depends on what you are sharing. +For saved searches, dashboards, and visualizations, the layout depends on the size of the panels. +For workpads, the layout depends on the size of the worksheet dimensions. -Generate and download PDF, PNG, and CSV files of dashboards, visualizations, **Canvas** workpads, and saved searches. +To change the output size, change the size of the browser, which resizes the shareable container before the report generates. It might take some trial and error before you're satisfied. -. Open the dashboard, visualization, **Canvas** workpad, or saved search. +In the following dashboard, the shareable container is highlighted: -. From the {kib} toolbar, click **Share**, then select one of the following options: +[role="screenshot"] +image::user/reporting/images/shareable-container.png["Shareable Container"] + +. Open the main menu, then open the saved search, dashboard, visualization, or workpad you want to share. + +. From the toolbar, click *Share*, then select one of the following options: + +** **PDF Reports** — Generates a PDF file of the dashboard, visualization, or workpad. -** **PDF Reports** — Generates a PDF file of the dashboard, visualization, or **Canvas** workpad. ** **PNG Reports** — Generates a PNG file of the dashboard or visualization. + ** **CSV Reports** — Generates a CSV report of the saved search. -. Generate the report. +. If you are creating a PDF report of a dashboard, select *Optimize for printing* to create a printer-friendly PDF with multiple A4 portrait pages and two visualizations per page. + -When the report completes, a notification appears. +NOTE: When you create a dashboard report that includes a data table or saved search, the PDF includes only the visible data. -. Click **Download report**. +. If you are creating a PDF report of a workpad, select *Full page layout* to create a PDF without margins that surround the workpad. -NOTE: When you create a dashboard report that includes a data table or saved search, the PDF includes only the visible data. +. Generate the report. + +. When the report generates, a message appears. On the message, click **Download report**. + +. To view and manage reports, open the main menu, then click *Stack Management > Reporting*. [float] -[[reporting-layout-sizing]] -== Layout and sizing -The layout and size of the PDF or PNG image depends on the {kib} app -with which the Reporting plugin is integrated. For *Canvas*, the -worksheet dimensions determine the size for reports. In other apps, -the dimensions are taken on the fly by looking at -the size of the visualization elements or panels on the page. - -The size dimensions are part of the reporting job parameters. Therefore, to -make the report output larger or smaller, you can change the size of the browser. -This resizes the shareable container before generating the -report, so the desired dimensions are passed in the job parameters. - -In the following {kib} dashboard, the shareable container is highlighted. -The shareable container is captured when you click -*Generate* or *Copy POST URL* from the *Share* menu. It might take some trial and error -before you're satisfied with the layout and dimensions in the -PNG or PDF image. +[[share-a-direct-link]] +== Share a direct link -[role="screenshot"] -image::user/reporting/images/shareable-container.png["Shareable Container"] +Share a direct link to a saved search, dashboard, or visualization. To access the shared object, authentication is required. +. Open the main menu, then open the saved search, dashboard, or visualization you want to share. +. From the toolbar, click *Share*, then select *Permalinks*. -[float] -[[optimize-pdf]] -== Optimize PDF for print—dashboard only +. Specify how you want to generate the link: + +* To display only the current state of the object, select *Snapshot*. -To create a printer-friendly PDF with multiple A4 portrait pages and two visualizations per page, turn on *Optimize for printing*. +* To display up-to-date changes, select *Saved object*. +* To generate a shortened link, select *Short URL*. + +* To automatically log in anonymous users when you have multiple authentication providers enabled, select *Public URL*. ++ [role="screenshot"] -image::user/reporting/images/preserve-layout-switch.png["Share"] +image::images/permalink-public-url.png[Permalink share menu with Public URL option highlighted] ++ +NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to share. +. Click *Copy link*. [float] -[[full-page-pdf]] -== Full page PDF layout —Canvas only +[[download-as-json]] +== Create a JSON file -To create a PDF without margins surrounding the Canvas workpad, turn on *Full page layout* before generating the PDF. +Create a JSON file for a workpad. -[role="screenshot"] -image::user/reporting/images/canvas-full-page-layout.png["Full Page Layout"] +. Open the main menu, then click *Canvas*. + +. Open the workpad you want to share. + +. From the toolbar, click *Share*, then select *Download as JSON*. + +[float] +[[add-workpad-website]] +== Share workpads on a website + +beta[] *Canvas* allows you to create _shareables_, which are workpads that you download and securely share on a website. +To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. + +. Open the main menu, then click *Canvas*. + +. Open the workpad you want to share. + +. Click *Share > Share on a website*. + +. Follow the instructions. + +. To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters. ++ +To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. *Canvas* does not display elements that manipulate the data on the workpad. ++ +NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website. +. To change the settings, click the settings icon, then choose the settings you want to use. [float] -[[manage-report-history]] -== View and manage report history +[[embed-code]] +== Embed code + +Display your dashboard or visualization on an internal company website or personal web page with an iframe. Embedding other {kib} objects is generally supported, but you might need to manually craft the proper HTML code. + +Some users might not have access to the dashboard or visualization. For more information, refer to <> and <>. + +. Open the main menu, then open the dashboard or visualization you want to share. + +. Click *Share > Embed code*. + +. Specify how you want to generate the code: + +* To display only the current state, select *Snapshot*. + +* To display up-to-date changes, select *Saved object*. + +* Select the dashboard or visualization elements you want to include. + +* To generate a shortened link, select *Short URL*. + +* To automatically log in anonymous users when you have multiple authentication providers enabled, select *Public URL*. ++ +[role="screenshot"] +image::images/embed-code-public-url.png[Embed code share menu with Public URL option highlighted] ++ +NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to embed. -For a list of your reports, open the main menu, then click *Stack Management > Reporting*. -From this view, you can monitor the status of a report and -download reports that you previously generated. +. Click *Copy iFrame code*. -- include::automating-report-generation.asciidoc[] -include::configuring-reporting.asciidoc[] -include::chromium-sandbox.asciidoc[] include::reporting-troubleshooting.asciidoc[] -include::development/index.asciidoc[] diff --git a/docs/user/reporting/network-policy.asciidoc b/docs/user/reporting/network-policy.asciidoc deleted file mode 100644 index 782473a3b0f189..00000000000000 --- a/docs/user/reporting/network-policy.asciidoc +++ /dev/null @@ -1,71 +0,0 @@ -[role="xpack"] -[[reporting-network-policy]] -=== Restrict requests with a Reporting network policy - -When Reporting generates PDF reports, it uses the Chromium browser to fully load the {kib} page on the server. This -potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a -field formatted as an image, or to show an image in a Markdown visualization. - -If the Chromium browser is asked to send a request that violates the network policy, Reporting stops processing the page -before the request goes out, and the report is marked as a failure. Additional information about the event is in -the Kibana server logs. - -[NOTE] -============ -{kib} installations are not designed to be publicly accessible over the Internet. The Reporting network policy and other capabilities -of the Elastic Stack security features do not change this condition. -============ - -==== Configure a Reporting network policy - -You configure the network policy by specifying the `xpack.reporting.capture.networkPolicy.rules` setting in `kibana.yml`. A policy is specified as -an array of objects that describe what to allow or deny based on a host or protocol. If a host or protocol -is not specified, the rule matches any host or protocol. - -The rule objects are evaluated sequentially from the beginning to the end of the array, and continue until there is a matching rule. -If no rules allow a request, the request is denied. - -[source,yaml] -------------------------------------------------------- -# Only allow requests to placeholder.com -xpack.reporting.capture.networkPolicy: - rules: [ { allow: true, host: "placeholder.com" } ] -------------------------------------------------------- - -[source,yaml] -------------------------------------------------------- -# Only allow requests to https://placeholder.com -xpack.reporting.capture.networkPolicy: - rules: [ { allow: true, host: "placeholder.com", protocol: "https:" } ] -------------------------------------------------------- - -A final `allow` rule with no host or protocol will allow all requests that are not explicitly denied. - -[source,yaml] -------------------------------------------------------- -# Denies requests from http://placeholder.com, but anything else is allowed. -xpack.reporting.capture.networkPolicy: - rules: [{ allow: false, host: "placeholder.com", protocol: "http:" }, { allow: true }]; -------------------------------------------------------- - -A network policy can be composed of multiple rules. - -[source,yaml] -------------------------------------------------------- -# Allow any request to http://placeholder.com but for any other host, https is required -xpack.reporting.capture.networkPolicy - rules: [ - { allow: true, host: "placeholder.com", protocol: "http:" }, - { allow: true, protocol: "https:" }, - ] -------------------------------------------------------- - -[NOTE] -============ -The `file:` protocol is always denied, even if no network policy is configured. -============ - -==== Disable a Reporting network policy - -You can use the `xpack.reporting.capture.networkPolicy.enabled: false` setting to disable the network policy feature. The default for -this configuration property is `true`, so it is not necessary to explicitly enable it. diff --git a/docs/user/reporting/report-intervals.asciidoc b/docs/user/reporting/report-intervals.asciidoc deleted file mode 100644 index d826ef70f4ee3e..00000000000000 --- a/docs/user/reporting/report-intervals.asciidoc +++ /dev/null @@ -1,12 +0,0 @@ -[IMPORTANT] -=================== -The interval between report requests must be longer than the time it -takes to generate the reports--otherwise, the report queue can back up. To -avoid this, increase the time between report requests. - -By default, report generation times out if the report cannot be generated -within two minutes. If you are generating reports that contain many complex -visualizations or your machine is slow or under constant heavy load, it -might take longer than two minutes to generate a report. You can increase -the timeout by setting `xpack.reporting.queue.timeout` in `kibana.yml`. -=================== \ No newline at end of file diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index d6d6190c8504b9..4b59cad38fd9a1 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -8,7 +8,6 @@ Having trouble? Here are solutions to common problems you might encounter while using Reporting. * <> -* <> * <> * <> * <> @@ -26,40 +25,6 @@ When {kib} is running, navigate to the Report Listing page, and click *Run repor This will open up a diagnostic tool that checks various parts of the {kib} deployment and come up with any relevant recommendations. -[float] -[[reporting-troubleshooting-system-dependencies]] -=== System dependencies -Reporting launches a "headless" web browser called Chromium on the Kibana server. It is a custom build made by Elastic of an open source -project, and it is intended to have minimal dependencies on OS libraries. However, the Kibana server OS might still require additional -dependencies to run the Chromium executable. - -Make sure Kibana server OS has the appropriate packages installed for the distribution. - -If you are using CentOS/RHEL systems, install the following packages: - -* `ipa-gothic-fonts` -* `xorg-x11-fonts-100dpi` -* `xorg-x11-fonts-75dpi` -* `xorg-x11-utils` -* `xorg-x11-fonts-cyrillic` -* `xorg-x11-fonts-Type1` -* `xorg-x11-fonts-misc` -* `fontconfig` -* `freetype` - -If you are using Ubuntu/Debian systems, install the following packages: - -* `fonts-liberation` -* `libfontconfig1` - -If the system is missing dependencies, then Reporting will fail in a non-deterministic way. {kib} runs a self-test at server startup, and -if it encounters errors, logs them in the Console. Unfortunately, the error message does not include -information about why Chromium failed to run. The most common error message is `Error: connect ECONNREFUSED`, which indicates -that {kib} could not connect to the Chromium process. - -To troubleshoot the problem, start the {kib} server with environment variables that tell Chromium to print verbose logs. See the -<> for more information. - [float] [[reporting-troubleshooting-text-incorrect]] === Text rendered incorrectly in generated reports @@ -100,7 +65,7 @@ multiple instances to find the same job in these searches. Only the instance tha "processing" will actually execute the report job. The other instances that unsuccessfully tried to make the same update will log something similar to this: -[source] +[source,text] -------------------------------------------------------------------------------- StatusCodeError: [version_conflict_engine_exception] [...]: version conflict, required seqNo [6124], primary term [1]. current document has seqNo [6125] and primary term [1], with { ... } status: 409, diff --git a/docs/user/reporting/script-example.asciidoc b/docs/user/reporting/script-example.asciidoc index 382d658a18dc96..1d8e824798e757 100644 --- a/docs/user/reporting/script-example.asciidoc +++ b/docs/user/reporting/script-example.asciidoc @@ -1,12 +1,7 @@ -To automatically generate reports from a script, you'll make a request to the `POST` URL. -The response from this request will be JSON, and will contain a `path` property with a -URL to use to download the generated report. Use the `GET` method in the HTTP request to -download the report. +To automatically generate reports from a script, make a request to the `POST` URL. The request returns a JSON and contains a `path` property with a +URL that you use to download the report. Use the `GET` method in the HTTP request to download the report. -The request method must be `POST` and it must include a `kbn-xsrf` header for Kibana -to allow the request. - -The following example queues CSV report generation using the `POST` URL with cURL: +To queue CSV report generation using the `POST` URL with cURL: ["source","sh",subs="attributes"] --------------------------------------------------------- @@ -18,14 +13,12 @@ curl \ --------------------------------------------------------- // CONSOLE -<1> `POST` method is required. -<2> Provide user credentials for a user with permission to access Kibana and -{report-features}. -<3> The `kbn-xsrf` header is required for all `POST` requests to Kibana. For more information, see <>. -<4> The POST URL. You can copy and paste the URL for any report from the Kibana UI. +<1> The required `POST` method. +<2> The user credentials for a user with permission to access {kib} and {report-features}. +<3> The required `kbn-xsrf` header for all `POST` requests to {kib}. For more information, refer to <>. +<4> The POST URL. You can copy and paste the URL for any report. -Here is an example response for a successfully queued report: +An example response for a successfully queued report: [source,json] --------------------------------------------------------- @@ -44,6 +37,5 @@ Here is an example response for a successfully queued report: --------------------------------------------------------- // CONSOLE -<1> The relative path on the Kibana host for downloading the report. -<2> (Not included in the example) Internal representation of the reporting job, as -found in the `.reporting-*` index. +<1> The relative path on the {kib} host for downloading the report. +<2> (Not included in the example) Internal representation of the reporting job, as found in the `.reporting-*` index. diff --git a/docs/user/reporting/watch-example.asciidoc b/docs/user/reporting/watch-example.asciidoc index 253722fefecc0e..909efa55ca9fbb 100644 --- a/docs/user/reporting/watch-example.asciidoc +++ b/docs/user/reporting/watch-example.asciidoc @@ -1,10 +1,4 @@ -To automatically generate reports with a watch, you need to configure -{watcher} to trust the {kib} server’s certificate. For more information, -see {kibana-ref}/secure-reporting.html[Securing Reporting]. - -To configure a watch to email reports, you use the `reporting` attachment type -in an `email` action. For more information, see -{ref}/actions-email.html#configuring-email[Configuring email accounts]. +To configure a watch to email reports, use the `reporting` attachment type in an `email` action. For more information, refer to {ref}/actions-email.html#configuring-email[Configuring email accounts]. For example, the following watch generates a PDF report and emails the report every hour: @@ -44,28 +38,18 @@ PUT _watcher/watch/error_report --------------------------------------------------------- // CONSOLE -<1> You must configure at least one email account to enable Watcher to send email. -For more information, see -{ref}/actions-email.html#configuring-email[Configuring email accounts]. -<2> This is an example POST URL. You can copy and paste the URL for any -report from the Kibana UI. -<3> Optional, default is 40 -<4> Optional, default is 15s -<5> Provide user credentials for a user with permission to access Kibana and -the {report-features}. -//For more information, see <>. -//<>. +<1> Configure at least one email account to enable Watcher to send email. For more information, refer to {ref}/actions-email.html#configuring-email[Configuring email accounts]. +<2> An example POST URL. You can copy and paste the URL for any report. +<3> Optional, default is `40`. +<4> Optional, default is `15s`. +<5> User credentials for a user with permission to access {kib} and the {report-features}. For more information, refer to <>. [NOTE] ==== -Reporting is integrated with Watcher only as an email attachment type. +*Reporting* is integrated with Watcher only as an email attachment type. -The report Generation URL might contain date-math expressions -that cause the watch to fail with a `parse_exception`. -Remove curly braces `{` `}` from date-math expressions and -URL-encode characters to avoid this. -For example: `...(range:(%27@timestamp%27:(gte:now-15m%2Fd,lte:now%2Fd))))...` +The report generation URL might contain date-math expressions that cause the watch to fail with a `parse_exception`. To avoid a failed watch, remove curly braces `{` `}` from date-math expressions and URL-encode characters. +For example, `...(range:(%27@timestamp%27:(gte:now-15m%2Fd,lte:now%2Fd))))...` -For more information about configuring watches, see -{ref}/how-watcher-works.html[How Watcher works]. +For more information about configuring watches, refer to {ref}/how-watcher-works.html[How Watcher works]. ==== diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 54142a6fe39e34..faa980fe833cb2 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -4,6 +4,8 @@ ++++ Authentication ++++ +:keywords: administrator, concept, security, authentication +:description: A list of the supported authentication mechanisms in {kib}. {kib} supports the following authentication mechanisms: @@ -16,6 +18,8 @@ - <> - <> - <> +- <> + For an introduction to {kib}'s security features, including the login process, refer to <>. @@ -395,17 +399,7 @@ xpack.security.authc.providers: One of the most popular use cases for anonymous access is when you embed {kib} into other applications and don't want to force your users to log in to view it. If you configured {kib} to use anonymous access as the sole authentication mechanism, you don't need to do anything special while embedding {kib}. -If you have multiple authentication providers enabled, and you want to automatically log in anonymous users when embedding dashboards and visualizations: - -. Open the main menu, then click *Dashboard* or *Visualize Library*. -. Open then dashboard or visualization you want to embed. -. Open the *Share* menu, then click *Embed code > Public URL*. -+ -You can also use *Public URL* when you're generating permanent links to dashboards, visualizations, and saved searches. -+ -NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to embed or share. -+ -For more information, refer to <>. +For information on how to embed, refer to <>. [float] [[anonymous-access-session]] @@ -437,3 +431,42 @@ xpack.security.authc.http.schemes: [apikey, basic, something-custom] -------------------------------------------------------------------------------- With this configuration, you can send requests to {kib} with the `Authorization` header using `ApiKey`, `Basic` or `Something-Custom` HTTP schemes (case insensitive). Under the hood, {kib} relays this header to {es}, then {es} authenticates the request using the credentials in the header. + +[float] +[[embedded-content-authentication]] +==== Embedded content authentication + +Once you create a dashboard or a visualization, you might want to share it with your colleagues or friends. The easiest way to do this is to share a direct link to your dashboard or visualization. However, some users might not have access to your {kib}. With the {kib} embedding functionality, you can display the content you created in {kib} to an internal company website or a personal web page. + +[[embedding-cookies]] +To minimize security risk, embedding with iframes requires careful consideration. By default, modern web browsers enforce the +https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy[same-origin policy] to restrict the behavior of framed pages. When +{stack-security-features} are enabled on your cluster, make sure the browsers can transmit session cookies to a {kib} server. The setting you need to be aware of is <>. To support modern browsers, you must set it to `None`: + +[source,yaml] +-- +xpack.security.sameSiteCookies: "None" +-- + +For more information about possible values and implications, refer to <>. +For more information about iframe and cookies, refer to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe[iframe] and https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite[SameSite cookies]. + +If you're embedding {kib} in a website that supports Single Sign-On with SAML, OpenID Connect, Kerberos, or PKI, it's highly advisable to configure {kib} as a part of the Single Sign-On setup. Operating in a single and properly configured security domain provides you with the most secure and seamless user experience. + +If you have multiple authentication providers enabled, and you want to automatically log in anonymous users when embedding anything other than dashboards and visualizations, then you will need to add the `auth_provider_hint=` query string parameter to the {kib} URL that you're embedding. + +For example, if you craft the iframe code to embed {kib}, it might look like this: + +```html + +``` + +To make this iframe leverage anonymous access automatically, you will need to modify a link to {kib} in the `src` iframe attribute to look like this: + +```html + +``` + +NOTE: `auth_provider_hint` query string parameter goes *before* the hash URL fragment. + +For more information on how to embed, refer to <>. diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc deleted file mode 100644 index ab25dddd046945..00000000000000 --- a/docs/user/security/reporting.asciidoc +++ /dev/null @@ -1,213 +0,0 @@ -[role="xpack"] -[[secure-reporting]] -=== Reporting and security - -Reporting operates by creating and updating documents in {es} in response to -user actions in {kib}. - -To use {report-features} with {security-features} enabled, you need to -<>. -If you are automatically generating reports with -{ref}/xpack-alerting.html[{watcher}], you also need to configure {watcher} -to trust the {kib} server's certificate. -//// -For more information, see -<>. -//// - -[[reporting-app-users]] -Access to reporting features is limited to privileged users. In older versions of Kibana, you could only grant -users the privilege by assigning them the `reporting_user` role in Elasticsearch. In 7.14 and above, you have -the option to create your own roles that grant access to reporting features using <>. - -It is recommended that you set `xpack.reporting.roles.enabled: false` in your kibana.yml to begin using Kibana -privileges. This will allow users to only see Reporting widgets in applications when they have privilege to use -them. - -[NOTE] -============================================================================ -The default value of `xpack.reporting.roles.enabled` is `true` for 7.x versions of Kibana. To migrate users to the -new method of securing access to *Reporting*, you must explicitly set `xpack.reporting.roles.enabled: false` in -`kibana.yml`. In the next major version of Kibana, having this set to `false` will be the only valid configuration. -============================================================================ - -This document discusses how to create a role that grants access to reporting features using the new method of -Kibana application privileges. - -[float] -[[reporting-roles-management-ui]] -=== Create the role in the `native` realm - -To create roles, use the *Roles* UI or <>. This example shows how to -create a role that grants reporting feature privileges in {kib} applications. - -. Open the main menu, then click *Stack Management > Roles*. - -. Click *Create role*, then give the role a name, for example, `custom_reporting_user`. - -. Specify the indices and privileges. -+ -Access to data is an index-level privilege, so in *Create role*, -add a line for each index that contains the data for the report and give each -index `read` and `view_index_metadata` privileges. -For more information, see {ref}/security-privileges.html[Security privileges]. -+ -[role="screenshot"] -image::user/security/images/reporting-privileges-example.png["Reporting privileges"] - -. Add space privileges for the {kib} applications that allow access to the reporting options. -+ -To allow users to create CSV reports in *Discover*, or PDF reports in *Canvas*, -*Visualize Library*, and *Dashboard*, click *Add Kibana privilege* for each application, -then select the privileges to generate -reports. For example, select *All* privileges for all features, or *Customize* to grant -the privilege to generate reports for only specific applications. -+ -[role="screenshot"] -image::user/security/images/reporting-custom-role.png["Reporting custom role"] -+ -[NOTE] -============================================================================ -Granting users access to reporting features in any application also grants them access to manage their reports in *Stack Management > Reporting*. -============================================================================ -+ -. Save your new role. - -. Open the main menu, then click *Stack Management > Users*, add a new user, and assign the user -your new `custom_reporting_user` role. - -[float] -[[reporting-roles-user-api]] -==== With the user API -This example uses the {ref}/security-api-put-role.html[role API] to create a role that -grants the privilege to generate reports in *Canvas*, *Discover*, *Visualize Library*, and *Dashboard*. -This role is meant to be granted to users in combination with other roles that grant read access -to the data in {es}, and at least read access in the applications -where they'll generate reports. - -[source, sh] ---------------------------------------------------------------- -POST /_security/role/custom_reporting_user -{ - metadata: {}, - elasticsearch: { cluster: [], indices: [], run_as: [] }, - kibana: [ - { - base: [], - feature: { - dashboard: [ - 'generate_report', <1> - 'download_csv_report' <2> - ], - discover: ['generate_report'], <3> - canvas: ['generate_report'], <4> - visualize: ['generate_report'], <5> - }, - spaces: ['*'], - } - ] -} ---------------------------------------------------------------- -// CONSOLE - -<1> Grants access to generate PNG and PDF reports in *Dashboard*. -<2> Grants access to download CSV files from saved search panels in *Dashboard*. -<3> Grants access to generate CSV reports from saved searches in *Discover*. -<4> Grants access to generate PDF reports in *Canvas*. -<5> Grants access to generate PNG and PDF reports in *Visualize Library*. - -[float] -=== When using an external provider - -If you are using an external identity provider, such as -LDAP or Active Directory, you can either assign -roles on a per user basis, or assign roles to groups of users. By default, role -mappings are configured in -{ref}/mapping-roles.html[`config/role_mapping.yml`]. -For example, the following snippet assigns the user named Bill Murray the -`kibana_admin` and `reporting_user` roles: - -[source,yaml] --------------------------------------------------------------------------------- -kibana_admin: - - "cn=Bill Murray,dc=example,dc=com" -reporting_user: - - "cn=Bill Murray,dc=example,dc=com" --------------------------------------------------------------------------------- - -[float] -=== With a custom index - -If you are using a custom index, -the `xpack.reporting.index` setting should begin -with `.reporting-*`. The default {kib} system user has -`all` privileges against the `.reporting-*` pattern of indices. - -[source,js] -xpack.reporting.index: '.reporting-custom-index' - -If you use a different pattern for the `xpack.reporting.index` setting, -you must create a custom `kibana_system` user with appropriate access to the index, similar -to the following: - -. Open the main menu, then click *Stack Management > Roles*. -. Click *Create role*, then name the role `custom-reporting-user`. -. Specify the custom index and assign it the `all` index privilege. -. Open the main menu, then click *Stack Management > Users* and create a new user with -the `kibana_system` role and the `custom-reporting-user` role. -. Configure {kib} to use the new account: -[source,js] -elasticsearch.username: 'custom_kibana_system' - -[NOTE] -============================================================================ -Setting a custom index for *Reporting* is not supported in the next major version of Kibana. -============================================================================ - -[role="xpack"] -[[securing-reporting]] -=== Secure the reporting endpoints - -In a production environment, you should restrict access to -the reporting endpoints to authorized users. This requires that you: - -. Enable {stack-security-features} on your {es} cluster. For more information, -see {ref}/security-getting-started.html[Getting started with security]. -. Configure TLS/SSL encryption for the {kib} server. For more information, see -<>. -. Specify the {kib} server's CA certificate chain in `elasticsearch.yml`: -+ --- -If you are using your own CA to sign the {kib} server certificate, then you need -to specify the CA certificate chain in {es} to properly establish trust in TLS -connections between {watcher} and {kib}. If your CA certificate chain is -contained in a PKCS #12 trust store, specify it like so: - -[source,yaml] --------------------------------------------------------------------------------- -xpack.http.ssl.truststore.path: "/path/to/your/truststore.p12" -xpack.http.ssl.truststore.type: "PKCS12" -xpack.http.ssl.truststore.password: "optional decryption password" --------------------------------------------------------------------------------- - -Otherwise, if your CA certificate chain is in PEM format, specify it like so: - -[source,yaml] --------------------------------------------------------------------------------- -xpack.http.ssl.certificate_authorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] --------------------------------------------------------------------------------- - -For more information, see {ref}/notification-settings.html#ssl-notification-settings[the {watcher} HTTP TLS/SSL Settings]. --- - -. Add one or more users who have the permissions -necessary to use {kib} and {report-features}. For more information, see -<>. - -Once you've enabled SSL for {kib}, all requests to the reporting endpoints -must include valid credentials. For example, see the following page which -includes a watch that submits requests as the built-in `elastic` user: -<>. - -For more information about configuring watches, see -{ref}/how-watcher-works.html[How {watcher} works]. diff --git a/docs/user/setup.asciidoc b/docs/user/setup.asciidoc index bea13c1ef49b2c..7da0a969b53e8b 100644 --- a/docs/user/setup.asciidoc +++ b/docs/user/setup.asciidoc @@ -60,4 +60,11 @@ include::{kib-repo-dir}/setup/connect-to-elasticsearch.asciidoc[] include::{kib-repo-dir}/setup/upgrade.asciidoc[] -include::{kib-repo-dir}/setup/embedding.asciidoc[] +include::security/securing-kibana.asciidoc[] + +include::{kib-repo-dir}/setup/configuring-reporting.asciidoc[] + +include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1] +include::monitoring/monitoring-metricbeat.asciidoc[leveloffset=+2] +include::monitoring/viewing-metrics.asciidoc[leveloffset=+2] +include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2] diff --git a/jest.config.integration.js b/jest.config.integration.js index b6ecb4569b643a..8ff142714eebf9 100644 --- a/jest.config.integration.js +++ b/jest.config.integration.js @@ -16,13 +16,12 @@ module.exports = { testPathIgnorePatterns: preset.testPathIgnorePatterns.filter( (pattern) => !pattern.includes('integration_tests') ), - setupFilesAfterEnv: ['/packages/kbn-test/target/jest/setup/after_env.integration.js'], + setupFilesAfterEnv: [ + '/node_modules/@kbn/test/target_node/jest/setup/after_env.integration.js', + ], reporters: [ 'default', - [ - '/packages/kbn-test/target/jest/junit_reporter', - { reportName: 'Jest Integration Tests' }, - ], + ['@kbn/test/target_node/jest/junit_reporter', { reportName: 'Jest Integration Tests' }], ], coverageReporters: !!process.env.CI ? [['json', { file: 'jest-integration.json' }]] diff --git a/package.json b/package.json index 1111179fc816b6..1cc379fb807d07 100644 --- a/package.json +++ b/package.json @@ -99,11 +99,11 @@ "dependencies": { "@elastic/apm-rum": "^5.8.0", "@elastic/apm-rum-react": "^1.2.11", - "@elastic/charts": "30.1.0", + "@elastic/charts": "31.0.0", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13", "@elastic/ems-client": "7.14.0", - "@elastic/eui": "34.5.1", + "@elastic/eui": "34.5.2", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", @@ -447,7 +447,7 @@ "@babel/traverse": "^7.12.12", "@babel/types": "^7.12.12", "@bazel/ibazel": "^0.15.10", - "@bazel/typescript": "^3.5.1", + "@bazel/typescript": "^3.6.0", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.6.0", "@elastic/eslint-config-kibana": "link:bazel-bin/packages/elastic-eslint-config-kibana", @@ -462,7 +462,7 @@ "@kbn/dev-utils": "link:bazel-bin/packages/kbn-dev-utils", "@kbn/docs-utils": "link:bazel-bin/packages/kbn-docs-utils", "@kbn/es": "link:bazel-bin/packages/kbn-es", - "@kbn/es-archiver": "link:packages/kbn-es-archiver", + "@kbn/es-archiver": "link:bazel-bin/packages/kbn-es-archiver", "@kbn/eslint-import-resolver-kibana": "link:bazel-bin/packages/kbn-eslint-import-resolver-kibana", "@kbn/eslint-plugin-eslint": "link:bazel-bin/packages/kbn-eslint-plugin-eslint", "@kbn/expect": "link:bazel-bin/packages/kbn-expect", @@ -473,7 +473,7 @@ "@kbn/spec-to-console": "link:bazel-bin/packages/kbn-spec-to-console", "@kbn/storybook": "link:bazel-bin/packages/kbn-storybook", "@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools", - "@kbn/test": "link:packages/kbn-test", + "@kbn/test": "link:bazel-bin/packages/kbn-test", "@kbn/test-subj-selector": "link:bazel-bin/packages/kbn-test-subj-selector", "@loaders.gl/polyfills": "^2.3.5", "@microsoft/api-documenter": "7.7.2", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 38d3f28ec866b7..de7a27fd512769 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -20,6 +20,7 @@ filegroup( "//packages/kbn-dev-utils:build", "//packages/kbn-docs-utils:build", "//packages/kbn-es:build", + "//packages/kbn-es-archiver:build", "//packages/kbn-eslint-import-resolver-kibana:build", "//packages/kbn-eslint-plugin-eslint:build", "//packages/kbn-expect:build", @@ -52,6 +53,7 @@ filegroup( "//packages/kbn-std:build", "//packages/kbn-storybook:build", "//packages/kbn-telemetry-tools:build", + "//packages/kbn-test:build", "//packages/kbn-test-subj-selector:build", "//packages/kbn-tinymath:build", "//packages/kbn-ui-framework:build", diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel new file mode 100644 index 00000000000000..9b3db311afa248 --- /dev/null +++ b/packages/kbn-es-archiver/BUILD.bazel @@ -0,0 +1,100 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-es-archiver" +PKG_REQUIRE_NAME = "@kbn/es-archiver" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + "**/__fixtures__", + "**/__mocks__", + "**/__snapshots__" + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +SRC_DEPS = [ + "//packages/kbn-dev-utils", + "//packages/kbn-test", + "//packages/kbn-utils", + "@npm//@elastic/elasticsearch", + "@npm//aggregate-error", + "@npm//bluebird", + "@npm//chance", + "@npm//globby", + "@npm//json-stable-stringify", + "@npm//lodash", + "@npm//sinon", + "@npm//zlib", +] + +TYPES_DEPS = [ + "@npm//@types/bluebird", + "@npm//@types/chance", + "@npm//@types/jest", + "@npm//@types/json-stable-stringify", + "@npm//@types/lodash", + "@npm//@types/node", + "@npm//@types/sinon", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json index c86d94c70d7394..e8eb7b5f8f1c98 100644 --- a/packages/kbn-es-archiver/package.json +++ b/packages/kbn-es-archiver/package.json @@ -7,12 +7,5 @@ "types": "target/index.d.ts", "kibana": { "devOnly": true - }, - "scripts": { - "kbn:bootstrap": "rm -rf target && ../../node_modules/.bin/tsc", - "kbn:watch": "rm -rf target && ../../node_modules/.bin/tsc --watch" - }, - "dependencies": { - "@kbn/test": "link:../kbn-test" } -} \ No newline at end of file +} diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json index 0950cd39d0bee4..1bc93908a993e4 100644 --- a/packages/kbn-es-archiver/tsconfig.json +++ b/packages/kbn-es-archiver/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "./target", "target": "ES2019", "declaration": true, diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c6960621359c78..6627b644daec76 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -99,7 +99,7 @@ pageLoadAssetSize: watcher: 43598 runtimeFields: 41752 stackAlerts: 29684 - presentationUtil: 49767 + presentationUtil: 94301 spacesOss: 18817 indexPatternFieldEditor: 90489 osquery: 107090 @@ -110,4 +110,5 @@ pageLoadAssetSize: timelines: 230410 screenshotMode: 17856 visTypePie: 35583 + expressionRevealImage: 25675 cases: 144442 diff --git a/packages/kbn-storybook/ignore_not_found_export_plugin.ts b/packages/kbn-storybook/ignore_not_found_export_plugin.ts new file mode 100644 index 00000000000000..18769416f43c1a --- /dev/null +++ b/packages/kbn-storybook/ignore_not_found_export_plugin.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +// derived from https://github.com/TypeStrong/ts-loader/issues/653#issuecomment-390889335 +// +// This plugin suppresses the irritating TS-related warnings in Storybook HMR. + +import { Compiler, Stats } from 'webpack'; +// @ts-expect-error +import ModuleDependencyWarning from 'webpack/lib/ModuleDependencyWarning'; + +export class IgnoreNotFoundExportPlugin { + apply(compiler: Compiler) { + const messageRegExp = /export '.*'( \(reexported as '.*'\))? was not found in/; + + function doneHook(stats: Stats) { + stats.compilation.warnings = stats.compilation.warnings.filter(function (warn) { + if (warn instanceof ModuleDependencyWarning && messageRegExp.test(warn.message)) { + return false; + } + return true; + }); + } + + if (compiler.hooks) { + compiler.hooks.done.tap('IgnoreNotFoundExportPlugin', doneHook); + } else { + compiler.plugin('done', doneHook); + } + } +} diff --git a/packages/kbn-storybook/webpack.config.ts b/packages/kbn-storybook/webpack.config.ts index 41d3ee1f7ee5c3..97fbf40468429d 100644 --- a/packages/kbn-storybook/webpack.config.ts +++ b/packages/kbn-storybook/webpack.config.ts @@ -12,6 +12,7 @@ import { resolve } from 'path'; import { Configuration, Stats } from 'webpack'; import webpackMerge from 'webpack-merge'; import { REPO_ROOT } from './lib/constants'; +import { IgnoreNotFoundExportPlugin } from './ignore_not_found_export_plugin'; const stats = { ...Stats.presetToOptions('minimal'), @@ -19,7 +20,6 @@ const stats = { errorDetails: true, errors: true, moduleTrace: true, - warningsFilter: /(export .* was not found in)|(entrypoint size limit)/, }; // Extend the Storybook Webpack config with some customizations @@ -70,6 +70,7 @@ export default function ({ config: storybookConfig }: { config: Configuration }) }, ], }, + plugins: [new IgnoreNotFoundExportPlugin()], resolve: { extensions: ['.js', '.ts', '.tsx', '.json'], mainFields: ['browser', 'main'], diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel new file mode 100644 index 00000000000000..28c42a4c476846 --- /dev/null +++ b/packages/kbn-test/BUILD.bazel @@ -0,0 +1,153 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@npm//@babel/cli:index.bzl", "babel") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-test" +PKG_REQUIRE_NAME = "@kbn/test" + +SOURCE_FILES = glob( + [ + "src/**/*" + ], + exclude = [ + "**/*.test.*", + "**/*.snap", + "**/__fixture__/**", + "**/__fixtures__/**", + "**/__snapshots__/**", + ] +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "jest/package.json", + "jest-preset.js", + "jest.config.js", + "README.md", + "package.json", +] + +SRC_DEPS = [ + "//packages/kbn-dev-utils", + "//packages/kbn-i18n", + "//packages/kbn-std", + "//packages/kbn-utils", + "@npm//@elastic/elasticsearch", + "@npm//axios", + "@npm//@babel/traverse", + "@npm//chance", + "@npm//del", + "@npm//enzyme", + "@npm//execa", + "@npm//exit-hook", + "@npm//form-data", + "@npm//globby", + "@npm//history", + "@npm//jest", + "@npm//jest-cli", + "@npm//jest-snapshot", + "@npm//@jest/reporters", + "@npm//joi", + "@npm//mustache", + "@npm//parse-link-header", + "@npm//prettier", + "@npm//react-dom", + "@npm//react-redux", + "@npm//react-router-dom", + "@npm//redux", + "@npm//rxjs", + "@npm//strip-ansi", + "@npm//xmlbuilder", + "@npm//xml2js", +] + +TYPES_DEPS = [ + "@npm//@types/chance", + "@npm//@types/enzyme", + "@npm//@types/history", + "@npm//@types/jest", + "@npm//@types/joi", + "@npm//@types/lodash", + "@npm//@types/mustache", + "@npm//@types/node", + "@npm//@types/parse-link-header", + "@npm//@types/prettier", + "@npm//@types/react-dom", + "@npm//@types/react-redux", + "@npm//@types/react-router-dom", + "@npm//@types/xml2js", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +babel( + name = "target_node", + data = DEPS + [ + ":srcs", + "babel.config.js", + ], + output_dir = True, + # the following arg paths includes $(execpath) as babel runs on the sandbox root + args = [ + "./%s/src" % package_name(), + "--config-file", + "./%s/babel.config.js" % package_name(), + "--out-dir", + "$(@D)", + "--extensions", + ".ts,.js,.tsx", + "--quiet" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + declaration_dir = "target_types", + emit_declaration_only = True, + incremental = True, + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":target_node", ":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 5baff607704c78..c84fe3f7a55b05 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -9,8 +9,6 @@ // For a detailed explanation regarding each configuration property, visit: // https://jestjs.io/docs/en/configuration.html -const { resolve } = require('path'); - module.exports = { // The directory where Jest should output its coverage files coverageDirectory: '/target/kibana-coverage/jest', @@ -30,13 +28,16 @@ module.exports = { moduleNameMapper: { '@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1', '@elastic/eui$': '/node_modules/@elastic/eui/test-env', - '\\.module.(css|scss)$': '/packages/kbn-test/target/jest/mocks/css_module_mock.js', - '\\.(css|less|scss)$': '/packages/kbn-test/target/jest/mocks/style_mock.js', + '\\.module.(css|scss)$': + '/node_modules/@kbn/test/target_node/jest/mocks/css_module_mock.js', + '\\.(css|less|scss)$': '/node_modules/@kbn/test/target_node/jest/mocks/style_mock.js', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': - '/packages/kbn-test/target/jest/mocks/file_mock.js', - '\\.ace\\.worker.js$': '/packages/kbn-test/target/jest/mocks/worker_module_mock.js', - '\\.editor\\.worker.js$': '/packages/kbn-test/target/jest/mocks/worker_module_mock.js', - '^(!!)?file-loader!': '/packages/kbn-test/target/jest/mocks/file_mock.js', + '/node_modules/@kbn/test/target_node/jest/mocks/file_mock.js', + '\\.ace\\.worker.js$': + '/node_modules/@kbn/test/target_node/jest/mocks/worker_module_mock.js', + '\\.editor\\.worker.js$': + '/node_modules/@kbn/test/target_node/jest/mocks/worker_module_mock.js', + '^(!!)?file-loader!': '/node_modules/@kbn/test/target_node/jest/mocks/file_mock.js', '^src/core/(.*)': '/src/core/$1', '^src/plugins/(.*)': '/src/plugins/$1', }, @@ -45,20 +46,20 @@ module.exports = { modulePathIgnorePatterns: ['__fixtures__/', 'target/'], // Use this configuration option to add custom reporters to Jest - reporters: ['default', resolve(__dirname, './target/jest/junit_reporter')], + reporters: ['default', '@kbn/test/target_node/jest/junit_reporter'], // The paths to modules that run some code to configure or set up the testing environment before each test setupFiles: [ - '/packages/kbn-test/target/jest/setup/babel_polyfill.js', - '/packages/kbn-test/target/jest/setup/polyfills.js', - '/packages/kbn-test/target/jest/setup/enzyme.js', + '/node_modules/@kbn/test/target_node/jest/setup/babel_polyfill.js', + '/node_modules/@kbn/test/target_node/jest/setup/polyfills.js', + '/node_modules/@kbn/test/target_node/jest/setup/enzyme.js', ], // A list of paths to modules that run some code to configure or set up the testing framework before each test setupFilesAfterEnv: [ - '/packages/kbn-test/target/jest/setup/setup_test.js', - '/packages/kbn-test/target/jest/setup/mocks.js', - '/packages/kbn-test/target/jest/setup/react_testing_library.js', + '/node_modules/@kbn/test/target_node/jest/setup/setup_test.js', + '/node_modules/@kbn/test/target_node/jest/setup/mocks.js', + '/node_modules/@kbn/test/target_node/jest/setup/react_testing_library.js', ], // A list of paths to snapshot serializer modules Jest should use for snapshot testing @@ -85,7 +86,7 @@ module.exports = { // A map from regular expressions to paths to transformers transform: { - '^.+\\.(js|tsx?)$': '/packages/kbn-test/target/jest/babel_transform.js', + '^.+\\.(js|tsx?)$': '/node_modules/@kbn/test/target_node/jest/babel_transform.js', '^.+\\.txt?$': 'jest-raw-loader', '^.+\\.html?$': 'jest-raw-loader', }, @@ -109,5 +110,5 @@ module.exports = { ], // A custom resolver to preserve symlinks by default - resolver: '/packages/kbn-test/target/jest/setup/preserve_symlinks_resolver.js', + resolver: '/node_modules/@kbn/test/target_node/jest/setup/preserve_symlinks_resolver.js', }; diff --git a/packages/kbn-test/jest/package.json b/packages/kbn-test/jest/package.json index c8b50f7b1b5ba6..aa0ba838736845 100644 --- a/packages/kbn-test/jest/package.json +++ b/packages/kbn-test/jest/package.json @@ -1,4 +1,4 @@ { - "main": "../target/jest", - "types": "../target/types/jest/index.d.ts" + "main": "../target_node/jest", + "types": "../target_types/jest/index.d.ts" } diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index aaff513f1591f2..c937d1e0be85c1 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -3,14 +3,9 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target", - "types": "./target/types", - "scripts": { - "build": "node scripts/build", - "kbn:bootstrap": "node scripts/build --source-maps", - "kbn:watch": "node scripts/build --watch --source-maps" - }, + "main": "./target_node", + "types": "./target_types", "kibana": { "devOnly": true } -} \ No newline at end of file +} diff --git a/packages/kbn-test/scripts/build.js b/packages/kbn-test/scripts/build.js deleted file mode 100644 index 0be9d96ad6d58f..00000000000000 --- a/packages/kbn-test/scripts/build.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const { resolve } = require('path'); - -const del = require('del'); -const supportsColor = require('supports-color'); -const { run, withProcRunner } = require('@kbn/dev-utils'); - -const ROOT_DIR = resolve(__dirname, '..'); -const BUILD_DIR = resolve(ROOT_DIR, 'target'); - -const padRight = (width, str) => - str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; - -run( - async ({ log, flags }) => { - await withProcRunner(log, async (proc) => { - log.info('Deleting old output'); - await del(BUILD_DIR); - - const cwd = ROOT_DIR; - const env = { ...process.env }; - if (supportsColor.stdout) { - env.FORCE_COLOR = 'true'; - } - - log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); - await Promise.all([ - proc.run(padRight(10, `babel`), { - cmd: 'babel', - args: [ - 'src', - '--config-file', - require.resolve('../babel.config.js'), - '--out-dir', - BUILD_DIR, - '--extensions', - '.ts,.js,.tsx', - ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE - ? [] - : ['--source-maps', 'inline']), - ], - wait: true, - env, - cwd, - }), - - proc.run(padRight(10, 'tsc'), { - cmd: 'tsc', - args: [ - ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), - ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), - ], - wait: true, - env, - cwd, - }), - ]); - - log.success('Complete'); - }); - }, - { - description: 'Simple build tool for @kbn/i18n package', - flags: { - boolean: ['watch', 'source-maps'], - help: ` - --watch Run in watch mode - --source-maps Include sourcemaps - `, - }, - } -); diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 86cbc121703ecf..dea2ec9d1035e3 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -18,7 +18,7 @@ import { // @internal export { runTestsCli, processRunTestsCliOptions, startServersCli, processStartServersCliOptions }; -// @ts-expect-error not typed yet +// @ts-ignore not typed yet // @internal export { runTests, startServers } from './functional_tests/tasks'; diff --git a/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js b/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js index 90d19de897ad4f..b90cc413d3f0d4 100644 --- a/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js +++ b/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js @@ -13,7 +13,7 @@ module.exports = { reporters: [ 'default', [ - `${REPO_ROOT}/packages/kbn-test/target/jest/junit_reporter`, + `${REPO_ROOT}/node_modules/@kbn/test/target_node/jest/junit_reporter`, { reportName: 'JUnit Reporter Integration Test', rootDirectory: resolve( diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 3cb68029d74cfd..22c502f53c03c9 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -1,24 +1,17 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "outDir": "./target/types", + "incremental": true, + "outDir": "./target_types", "stripInternal": true, - "emitDeclarationOnly": true, "declaration": true, "declarationMap": true, + "emitDeclarationOnly": true, + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../../../packages/kbn-test/src", - "types": [ - "jest", - "node" - ], + "types": ["jest", "node"] }, - "include": [ - "src/**/*", - "index.d.ts" - ], - "exclude": [ - "**/__fixtures__/**/*" - ] + "include": ["src/**/*", "index.d.ts"], + "exclude": ["**/__fixtures__/**/*"] } diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel index c04f88a42cce03..9096905a2586be 100644 --- a/packages/kbn-ui-shared-deps/BUILD.bazel +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -50,8 +50,6 @@ SRC_DEPS = [ "@npm//fflate", "@npm//jquery", "@npm//loader-utils", - # TODO: we can remove this once EUI patches the dependencies - "@npm//mdast-util-to-hast", "@npm//mini-css-extract-plugin", "@npm//moment", "@npm//moment-timezone", diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index a653fbc5e40bda..9e890f5bce4083 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -1917,95 +1917,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` /> - -
- - -
-
- - - - - -
-
-
-
-
diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index 2ce5cf2ee49336..eaddc6b94bd469 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -16,10 +16,8 @@ import { EuiListGroupItem, EuiShowFor, EuiCollapsibleNavProps, - EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { groupBy, sortBy } from 'lodash'; import React, { Fragment, useRef } from 'react'; import useObservable from 'react-use/lib/useObservable'; @@ -325,16 +323,6 @@ export function CollapsibleNav({ - {/* Quick addition of that "ADD DATA" button everyone wants :) Feel free to remove though. */} - - {/* Span fakes the nav group into not being the first item and therefore adding a top border */} - - - - - - - ); } diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 9206a4d1b99f1e..6bb714e9138383 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -51,9 +51,13 @@ export class DocLinksService { elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`, startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`, exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html`, + suricataModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-suricata.html`, + zeekModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-zeek.html`, }, auditbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}`, + auditdModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-auditd.html`, + systemModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-system.html`, }, enterpriseSearch: { base: `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}`, @@ -70,6 +74,9 @@ export class DocLinksService { heartbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/heartbeat/${DOC_LINK_VERSION}`, }, + libbeat: { + getStarted: `${ELASTIC_WEBSITE_URL}guide/en/beats/libbeat/${DOC_LINK_VERSION}/getting-started.html`, + }, logstash: { base: `${ELASTIC_WEBSITE_URL}guide/en/logstash/${DOC_LINK_VERSION}`, }, @@ -195,6 +202,10 @@ export class DocLinksService { siem: { guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, + ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`, + ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`, + detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`, + networkMap: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/conf-map-ui.html`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -252,6 +263,7 @@ export class DocLinksService { lensPanels: `${KIBANA_DOCS}lens.html`, maps: `${ELASTIC_WEBSITE_URL}maps`, vega: `${KIBANA_DOCS}vega.html`, + tsvbIndexPatternMode: `${KIBANA_DOCS}tsvb.html#tsvb-index-pattern-mode`, }, observability: { guide: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/index.html`, @@ -317,7 +329,7 @@ export class DocLinksService { }, apis: { bulkIndexAlias: `${ELASTICSEARCH_DOCS}indices-aliases.html`, - byteSizeUnits: `${ELASTICSEARCH_DOCS}common-options.html#byte-units`, + byteSizeUnits: `${ELASTICSEARCH_DOCS}api-conventions.html#byte-units`, createAutoFollowPattern: `${ELASTICSEARCH_DOCS}ccr-put-auto-follow-pattern.html`, createFollower: `${ELASTICSEARCH_DOCS}ccr-put-follow.html`, createIndex: `${ELASTICSEARCH_DOCS}indices-create-index.html`, @@ -341,7 +353,7 @@ export class DocLinksService { putSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, putWatch: `${ELASTICSEARCH_DOCS}watcher-api-put-watch.html`, simulatePipeline: `${ELASTICSEARCH_DOCS}simulate-pipeline-api.html`, - timeUnits: `${ELASTICSEARCH_DOCS}common-options.html#time-units`, + timeUnits: `${ELASTICSEARCH_DOCS}api-conventions.html#time-units`, updateTransform: `${ELASTICSEARCH_DOCS}update-transform.html`, }, plugins: { @@ -418,6 +430,9 @@ export class DocLinksService { upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`, upgradeElasticAgent712lower: `${FLEET_DOCS}upgrade-elastic-agent.html#upgrade-7.12-lower`, }, + ecs: { + guide: `${ELASTIC_WEBSITE_URL}guide/en/ecs/current/index.html`, + }, }, }); } @@ -448,9 +463,13 @@ export interface DocLinksStart { readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; + readonly suricataModule: string; + readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; + readonly auditdModule: string; + readonly systemModule: string; }; readonly metricbeat: { readonly base: string; @@ -467,6 +486,9 @@ export interface DocLinksStart { readonly heartbeat: { readonly base: string; }; + readonly libbeat: { + readonly getStarted: string; + }; readonly logstash: { readonly base: string; }; @@ -543,6 +565,10 @@ export interface DocLinksStart { readonly siem: { readonly guide: string; readonly gettingStarted: string; + readonly ml: string; + readonly ruleChangeLog: string; + readonly detectionsReq: string; + readonly networkMap: string; }; readonly query: { readonly eql: string; @@ -621,5 +647,8 @@ export interface DocLinksStart { upgradeElasticAgent: string; upgradeElasticAgent712lower: string; }>; + readonly ecs: { + readonly guide: string; + }; }; } diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 8b87c21e22fa47..f18dfb02fd41da 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -507,9 +507,13 @@ export interface DocLinksStart { readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; + readonly suricataModule: string; + readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; + readonly auditdModule: string; + readonly systemModule: string; }; readonly metricbeat: { readonly base: string; @@ -526,6 +530,9 @@ export interface DocLinksStart { readonly heartbeat: { readonly base: string; }; + readonly libbeat: { + readonly getStarted: string; + }; readonly logstash: { readonly base: string; }; @@ -602,6 +609,10 @@ export interface DocLinksStart { readonly siem: { readonly guide: string; readonly gettingStarted: string; + readonly ml: string; + readonly ruleChangeLog: string; + readonly detectionsReq: string; + readonly networkMap: string; }; readonly query: { readonly eql: string; @@ -680,6 +691,9 @@ export interface DocLinksStart { upgradeElasticAgent: string; upgradeElasticAgent712lower: string; }>; + readonly ecs: { + readonly guide: string; + }; }; } diff --git a/src/core/server/deprecations/README.mdx b/src/core/server/deprecations/README.mdx new file mode 100644 index 00000000000000..9a2ad689974e65 --- /dev/null +++ b/src/core/server/deprecations/README.mdx @@ -0,0 +1,210 @@ +--- +id: kibCoreDeprecationsService +slug: /kibana-dev-docs/services/deprecations-service +title: Core Deprecations service +summary: The Deprecations service helps surface deprecated configs and features for plugins to our users +date: 2021-06-27 +tags: ['kibana','dev', 'contributor', 'api docs'] +--- + +# Core Deprecations service +This guide is written in the format of a FAQ to help you get started using the deprecations service. +For more details on the service contract we have documented all the service apis and properties under the +deprecation service docs generated by in core. + +## What is the deprecations service? +The deprecations service provides a way for the Kibana platform to communicate deprecated features +and configs with its users. These deprecations are only communicated to the user if the deployment is using +these features, allowing for a user tailored experience for upgrading the stack version. + +The Upgrade Assistant (UA) is the main consumer of these deprecations. The UA displays them to users and signals to +Cloud that the Stack is ready for a major upgrade. + + +## Where are deprecations displayed to users? +The Upgrade Assistant (UA) in Kibana is consuming the core deprecations service in order to surface plugin +deprecations. Each deprecation is required to provide manual steps that we display in the UI. Additionally +each deprecation can provide an optional API to automatically resolve the deprecation. + +To check your plugin deprecations, go to the UA interface via **Stack Management > Stack > Upgrade Assistant** +and click on `View deprecations` under the Kibana section. + +To display deprecations in the UA set `xpack.upgrade_assistant.readonly: false` in the kibana configurations. +The UA is in read-only mode and will be enabled up until the last minor before the next major release. + +## How do I use this service for deprecated plugin configurations? +The deprecations service automatically hooks deprecated configs with the deprecations service. + +All the config deprecation functions (`unused`, `unusedFromRoot`, `rename`, `renameFromRoot`) accept an +optional parameter to customize the deprecation details. + +To read more about the deprecation functions check `/kibana-dev-docs/corePluginApi` in the new doc +system under `core.ConfigDeprecationFactory`. You can also check the `ConfigDeprecationFactory` +[interface docs](../../../../../packages/kbn-config/src/deprecation/types.ts). + +### Example +```ts +export const config: PluginConfigDescriptor = { + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('ui_metric.debug', 'usageCollection.uiCounters.debug', { + documentationUrl: 'elastic.co/some-url', + }), + ], +}; +``` + +The service will show the following deprecation details when the users set the above deprecated kibana +config `ui_metric.debug`. + +```ts +{ + deprecationsInfo:[{ + level: 'critical', + message: `"ui_metric.debug" is deprecated and has been replaced by "usageCollection.uiCounters.debug"`, + documentationUrl: 'elastic.co/some-url', + correctiveActions:{ + manualSteps: [ + `Replace "ui_metric.debug" with "usageCollection.uiCounters.debug" in the Kibana config file, CLI flag, or environment variable (in Docker only).`, + ] + }, + domainId: 'usageCollection', + }], +} +``` + +#### Custom deprecations for plugin configs +Custom config deprecation handling allows specifying the deprecation details via the `addDeprecation`. + +##### Example + +```ts +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + defaultAppId: true, + }, + schema: configSchema, + deprecations: () => [ + ( + completeConfig: Record, + rootPath: string, + addDeprecation: AddConfigDeprecation + ) => { + if ( + get(completeConfig, 'kibana.defaultAppId') === undefined && + get(completeConfig, 'kibana_legacy.defaultAppId') === undefined + ) { + return completeConfig; + } + addDeprecation({ + message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the "defaultRoute" advanced setting instead`, + correctiveActions: { + manualSteps: [ + 'Go to Stack Management > Advanced Settings', + 'Update the "defaultRoute" setting under the General section', + 'Remove "kibana.defaultAppId" from the kibana.yml config file', + ], + }, + }); + return completeConfig; + }, + ], +}; +``` + +## How do I add deprecations for non-config plugin deprecations? +Plugins are responsible for registering any deprecations during the `setup` lifecycle by using +the deprecations service. + +Examples of non-config deprecations include things like +- timelion sheets +- kibana_user security roles + +This service is not intended to be used for non-user facing deprecations or cases where the deprecation +cannot be 'detected' by this mechanism. + +### Usage +```ts +coreSetup.deprecations.registerDeprecations({ + getDeprecations: ({ esClient, savedObjectsClient }) => [{ ...`` }], +}); +``` + +The `getDeprecations` function is invoked when the user requests to see the deprecations affecting their deployment. +The function provides a context object which contains a scoped Elasticsearch client and a saved objects client. + +To check the full TS types of the service please check the [generated core docs](../../../../docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md). + +### Example +```ts +import { DeprecationsDetails, GetDeprecationsContext } from 'src/core/server'; + +async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise { + const deprecations: DeprecationsDetails[] = []; + const testDashboardUser = await getTestDashboardUser(savedObjectsClient); + + if (testDashboardUser) { + deprecations.push({ + message: 'User "test_dashboard_user" is using a deprecated role: "kibana_user"', + documentationUrl: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html', + level: 'critical', + correctiveActions: { + api: { + path: '/internal/security/users/test_dashboard_user', + method: 'POST', + body: { + username: 'test_dashboard_user', + roles: [ + 'machine_learning_user', + 'enrich_user', + 'kibana_admin' + ], + full_name: 'Alison Goryachev', + email: 'alisongoryachev@gmail.com', + metadata: {}, + enabled: true + } + }, + manualSteps: [ + 'Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.', + 'Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role.' + ] + }, + }); + } + + return deprecations; +} + ``` + +## How do I implement the API corrective action? +The deprecations API allows plugins to provide an API call that can be used to automatically fix specific deprecations. +To do so create a `PUT` or `POST` route in your plugin and specify data you want to be passed in the payload for the deprecation. + +In the example above, `/internal/security/users/test_dashboard_user` will be called when users click on `Quick Resolve` in the UA. The service will automatically pass the body provided in the api corrective action to provide context to the route for fixing the deprecation. + +The deprecations service expects a `200` status code to recognize the corrective action as a success. + +## How do I test my deprecations? +We recommend testing the route for your api corrective actions via unit and plugin functional tests. +To check the deprecation in the UI please check the UA page. + +To test that your logic registering the deprecations with the deprecations service during `setup` is valid we recommend adding +unit tests to check that the `getDeprecations` is returning the expected array of deprecations at different scenarios. + +Core provides mocks for the service which should help you focus on testing only your specific requirements. + +You can also use the deprecations service API; The deprecations service exposes an api `GET /api/deprecations/` which provides +a list of deprecations and possible corrective actions required to resolve these entries. The context is scoped to the requesting +user, hence a user with limited access might not be able to see all the deprecations affecting the deployment. + +Currently we do not have test objects to run functional tests against the Upgrade Assistant directly. + +## Should I use the service for all the deprecations before 8.0? +Yes. Using this service should help users find and resolve any issues specific to their deployment before upgrading. +We recommend adding a `documentationUrl` for every deprecation you expose to further assist our users if they need extra help. + +## Note on i18n +We have decided to support i18n to the exposed deprecations for a better user experience when using the UA. +We will inject `i18n` into the deprecation function to enable teams to use it before fully documenting its usage. +For context follow [this issue](https://github.com/elastic/kibana/issues/99072). diff --git a/src/dev/build/README.md b/src/dev/build/README.md index f6e11af67da33f..c499ef4a610832 100644 --- a/src/dev/build/README.md +++ b/src/dev/build/README.md @@ -12,8 +12,8 @@ node scripts/build --help # build a release version node scripts/build --release -# reuse already downloaded node executables, turn on debug logging, and only build the default distributable -node scripts/build --skip-node-download --debug --no-oss +# reuse already downloaded node executables, turn on debug logging +node scripts/build --skip-node-download --debug ``` # Fixing out of memory issues diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index e749af73241cf1..555df8981d70fb 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -26,8 +26,6 @@ it('build default and oss dist for current platform, without packages, by defaul expect(readCliArgs(['node', 'scripts/build'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -53,8 +51,6 @@ it('builds packages if --all-platforms is passed', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": true, @@ -80,8 +76,6 @@ it('limits packages if --rpm passed with --all-platforms', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--rpm'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -107,8 +101,6 @@ it('limits packages if --deb passed with --all-platforms', () => { expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--deb'])).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, @@ -135,8 +127,6 @@ it('limits packages if --docker passed with --all-platforms', () => { .toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -170,8 +160,6 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform ).toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -198,8 +186,6 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () => .toMatchInlineSnapshot(` Object { "buildOptions": Object { - "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index bbfbd3e6f8813a..08a375e8011e69 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -15,8 +15,6 @@ export function readCliArgs(argv: string[]) { const unknownFlags: string[] = []; const flags = getopts(argv, { boolean: [ - 'oss', - 'no-oss', 'skip-archives', 'skip-initialize', 'skip-generic-folders', @@ -48,7 +46,6 @@ export function readCliArgs(argv: string[]) { rpm: null, deb: null, 'docker-images': null, - oss: null, 'version-qualifier': '', }, unknown: (flag) => { @@ -94,8 +91,6 @@ export function readCliArgs(argv: string[]) { const buildOptions: BuildOptions = { isRelease: Boolean(flags.release), versionQualifier: flags['version-qualifier'], - buildOssDist: flags.oss !== false, - buildDefaultDist: !flags.oss, initialize: !Boolean(flags['skip-initialize']), downloadFreshNode: !Boolean(flags['skip-node-download']), createGenericFolders: !Boolean(flags['skip-generic-folders']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index f0403fac1e26b3..159281ed71db0e 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -13,8 +13,6 @@ import * as Tasks from './tasks'; export interface BuildOptions { isRelease: boolean; - buildOssDist: boolean; - buildDefaultDist: boolean; downloadFreshNode: boolean; initialize: boolean; createGenericFolders: boolean; @@ -37,8 +35,6 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions const run = createRunner({ config, log, - buildDefaultDist: options.buildDefaultDist, - buildOssDist: options.buildOssDist, }); /** diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index 3712e2230296a1..c727c26d7dcd30 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -33,8 +33,6 @@ if (showHelp) { build the Kibana distributable options: - --oss {dim Only produce the OSS distributable of Kibana} - --no-oss {dim Only produce the default distributable of Kibana} --skip-archives {dim Don't produce tar/zip archives} --skip-os-packages {dim Don't produce rpm/deb/docker packages} --all-platforms {dim Produce archives for all platforms, not just this one} diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts index fe328329d7df03..f92a91e2b7221e 100644 --- a/src/dev/build/lib/build.test.ts +++ b/src/dev/build/lib/build.test.ts @@ -43,40 +43,24 @@ beforeEach(() => { jest.clearAllMocks(); }); -const ossBuild = new Build(config, true); -const defaultBuild = new Build(config, false); - -describe('#isOss()', () => { - it('returns true for oss', () => { - expect(ossBuild.isOss()).toBe(true); - }); - - it('returns false for default build', () => { - expect(defaultBuild.isOss()).toBe(false); - }); -}); +const defaultBuild = new Build(config); describe('#getName()', () => { it('returns kibana for default build', () => { expect(defaultBuild.getName()).toBe('kibana'); }); - - it('returns kibana-oss for oss', () => { - expect(ossBuild.getName()).toBe('kibana-oss'); - }); }); describe('#getLogTag()', () => { it('returns string with build name in it', () => { expect(defaultBuild.getLogTag()).toContain(defaultBuild.getName()); - expect(ossBuild.getLogTag()).toContain(ossBuild.getName()); }); }); describe('#resolvePath()', () => { it('uses passed config to resolve a path relative to the repo', () => { - expect(ossBuild.resolvePath('bar')).toMatchInlineSnapshot( - `/build/kibana-oss/bar` + expect(defaultBuild.resolvePath('bar')).toMatchInlineSnapshot( + `/build/kibana/bar` ); }); @@ -89,28 +73,27 @@ describe('#resolvePath()', () => { describe('#resolvePathForPlatform()', () => { it('uses config.resolveFromRepo(), config.getBuildVersion(), and platform.getBuildName() to create path', () => { - expect(ossBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( - `/build/oss/kibana-8.0.0-linux-x86_64/foo/bar` + expect(defaultBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( + `/build/default/kibana-8.0.0-linux-x86_64/foo/bar` ); }); }); describe('#getPlatformArchivePath()', () => { it('creates correct path for different platforms', () => { - expect(ossBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-linux-x86_64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-linux-x86_64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-linux-aarch64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-linux-aarch64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( - `/target/kibana-oss-8.0.0-windows-x86_64.zip` + expect(defaultBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( + `/target/kibana-8.0.0-windows-x86_64.zip` ); }); describe('#getRootDirectory()', () => { it('creates correct root directory name', () => { - expect(ossBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-oss-8.0.0"`); expect(defaultBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-8.0.0"`); }); }); diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts index f4ccb30994eef5..c777ad18dc51f0 100644 --- a/src/dev/build/lib/build.ts +++ b/src/dev/build/lib/build.ts @@ -12,14 +12,10 @@ import { Config } from './config'; import { Platform } from './platform'; export class Build { - private name = this.oss ? 'kibana-oss' : 'kibana'; - private logTag = this.oss ? chalk`{magenta [kibana-oss]}` : chalk`{cyan [ kibana ]}`; + private name = 'kibana'; + private logTag = chalk`{cyan [ kibana ]}`; - constructor(private config: Config, private oss: boolean) {} - - isOss() { - return !!this.oss; - } + constructor(private config: Config) {} resolvePath(...args: string[]) { return this.config.resolveFromRepo('build', this.name, ...args); @@ -28,7 +24,7 @@ export class Build { resolvePathForPlatform(platform: Platform, ...args: string[]) { return this.config.resolveFromRepo( 'build', - this.oss ? 'oss' : 'default', + 'default', `kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`, ...args ); diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts index 0e3c00d220ad9a..2a08da2797a9de 100644 --- a/src/dev/build/lib/runner.test.ts +++ b/src/dev/build/lib/runner.test.ts @@ -45,7 +45,7 @@ beforeEach(() => { jest.clearAllMocks(); }); -const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) => { +const setup = async () => { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, @@ -55,55 +55,14 @@ const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) const run = createRunner({ config, log, - ...opts, }); return { config, run }; }; -describe('buildOssDist = true, buildDefaultDist = true', () => { +describe('default dist', () => { it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build), expect.any(Build)]); - }); - - it('calls local tasks twice, passing each build', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(2); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - }); -}); - -describe('just default dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { config, run } = await setup(); const mock = jest.fn(); @@ -118,52 +77,7 @@ describe('just default dist', () => { }); it('calls local tasks once, passing the default build', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (build.isOss()) { - throw new Error('expected build to be the default dist, not the oss dist'); - } - }); -}); - -describe('just oss dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build)]); - }); - - it('calls local tasks once, passing the oss build', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, - }); + const { config, run } = await setup(); const mock = jest.fn(); @@ -174,20 +88,12 @@ describe('just oss dist', () => { expect(mock).toHaveBeenCalledTimes(1); expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (!build.isOss()) { - throw new Error('expected build to be the oss dist, not the default dist'); - } }); }); describe('task rejection', () => { it('rejects, logs error, and marks error logged', async () => { - const { run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { run } = await setup(); const error = new Error('FOO'); expect(isErrorLogged(error)).toBe(false); @@ -213,10 +119,7 @@ describe('task rejection', () => { }); it('just rethrows errors that have already been logged', async () => { - const { run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, - }); + const { run } = await setup(); const error = markErrorLogged(new Error('FOO')); const promise = run({ diff --git a/src/dev/build/lib/runner.ts b/src/dev/build/lib/runner.ts index 015de6fe7e9ef7..1fccd884cc4f95 100644 --- a/src/dev/build/lib/runner.ts +++ b/src/dev/build/lib/runner.ts @@ -16,8 +16,6 @@ import { Config } from './config'; interface Options { config: Config; log: ToolingLog; - buildOssDist: boolean; - buildDefaultDist: boolean; } export interface GlobalTask { @@ -32,7 +30,7 @@ export interface Task { run(config: Config, log: ToolingLog, build: Build): Promise; } -export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Options) { +export function createRunner({ config, log }: Options) { async function execTask(desc: string, task: Task | GlobalTask, lastArg: any) { log.info(desc); log.indent(4); @@ -63,12 +61,7 @@ export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Op } const builds: Build[] = []; - if (buildDefaultDist) { - builds.push(new Build(config, false)); - } - if (buildOssDist) { - builds.push(new Build(config, true)); - } + builds.push(new Build(config)); /** * Run a task by calling its `run()` method with three arguments: diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index edff77d458f0f8..48d249ca374095 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -27,7 +27,6 @@ export const BuildKibanaPlatformPlugins: Task = { repoRoot: REPO_ROOT, outputRoot: build.resolvePath(), cache: false, - oss: build.isOss(), examples: false, watch: false, dist: true, diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts index e6305b3761a4f7..808903661a5950 100644 --- a/src/dev/build/tasks/build_packages_task.ts +++ b/src/dev/build/tasks/build_packages_task.ts @@ -63,7 +63,6 @@ export const BuildBazelPackages: Task = { await buildBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; @@ -75,7 +74,6 @@ export const BuildPackages: Task = { await buildNonBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts index e2d3ff7149c4c5..37c4becae76a83 100644 --- a/src/dev/build/tasks/create_archives_task.ts +++ b/src/dev/build/tasks/create_archives_task.ts @@ -77,14 +77,14 @@ export const CreateArchives: Task = { const metrics: CiStatsMetric[] = []; for (const { format, path, fileCount } of archives) { metrics.push({ - group: `${build.isOss() ? 'oss ' : ''}distributable size`, + group: `distributable size`, id: format, value: (await asyncStat(path)).size, }); metrics.push({ group: 'distributable file count', - id: build.isOss() ? 'oss' : 'default', + id: 'default', value: fileCount, }); } diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index b21a8484fa7104..37abcbad4466eb 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -15,21 +15,17 @@ export const InstallChromium = { description: 'Installing Chromium', async run(config, log, build) { - if (build.isOss()) { - return; - } else { - for (const platform of config.getNodePlatforms()) { - log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); + for (const platform of config.getNodePlatforms()) { + log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); - const { binaryPath$ } = installBrowser( - // TODO: https://github.com/elastic/kibana/issues/72496 - log, - build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), - platform.getName(), - platform.getArchitecture() - ); - await binaryPath$.pipe(first()).toPromise(); - } + const { binaryPath$ } = installBrowser( + // TODO: https://github.com/elastic/kibana/issues/72496 + log, + build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), + platform.getName(), + platform.getArchitecture() + ); + await binaryPath$.pipe(first()).toPromise(); } }, }; diff --git a/src/dev/build/tasks/license_file_task.ts b/src/dev/build/tasks/license_file_task.ts index 7e5ed8da0a27a9..ff33707476576f 100644 --- a/src/dev/build/tasks/license_file_task.ts +++ b/src/dev/build/tasks/license_file_task.ts @@ -8,23 +8,13 @@ import { write, read, Task } from '../lib'; -const LICENSE_SEPARATOR = `\n------------------------------------------------------------------------\n\n`; - export const UpdateLicenseFile: Task = { description: 'Updating LICENSE.txt file', async run(config, log, build) { const elasticLicense = await read(config.resolveFromRepo('licenses/ELASTIC-LICENSE-2.0.txt')); - if (build.isOss()) { - const ssplLicense = await read(config.resolveFromRepo('licenses/SSPL-LICENSE.txt')); - log.info('Copying dual-license to LICENSE.txt'); - await write( - build.resolvePath('LICENSE.txt'), - ssplLicense + LICENSE_SEPARATOR + elasticLicense - ); - } else { - log.info('Copying Elastic license to LICENSE.txt'); - await write(build.resolvePath('LICENSE.txt'), elasticLicense); - } + + log.info('Copying Elastic license to LICENSE.txt'); + await write(build.resolvePath('LICENSE.txt'), elasticLicense); }, }; diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 2ae882000cae00..99d0e1998e78a8 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -73,15 +73,12 @@ export const CreateDockerUBI: Task = { description: 'Creating Docker UBI image', async run(config, log, build) { - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - architecture: 'x64', - context: false, - ubi: true, - image: true, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + architecture: 'x64', + context: false, + ubi: true, + image: true, + }); }, }; @@ -95,19 +92,15 @@ export const CreateDockerContexts: Task = { dockerBuildDate, }); - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - ubi: true, - context: true, - image: false, - dockerBuildDate, - }); - await runDockerGenerator(config, log, build, { - ironbank: true, - context: true, - image: false, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + ubi: true, + context: true, + image: false, + }); + await runDockerGenerator(config, log, build, { + ironbank: true, + context: true, + image: false, + }); }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index a224793bace3f0..643080fda381f0 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -380,6 +380,16 @@ kibana_vars=( xpack.security.session.idleTimeout xpack.security.session.lifespan xpack.security.sessionTimeout + xpack.securitySolution.alertMergeStrategy + xpack.securitySolution.alertResultListDefaultDateRange + xpack.securitySolution.endpointResultListDefaultFirstPageIndex + xpack.securitySolution.endpointResultListDefaultPageSize + xpack.securitySolution.maxRuleImportExportSize + xpack.securitySolution.maxRuleImportPayloadBytes + xpack.securitySolution.maxTimelineImportExportSize + xpack.securitySolution.maxTimelineImportPayloadBytes + xpack.securitySolution.packagerTaskInterval + xpack.securitySolution.validateArtifactDownloads xpack.spaces.enabled xpack.spaces.maxSpaces xpack.task_manager.enabled diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index c72112b7b6b03d..97fd7404097410 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -43,24 +43,18 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.ubi) imageFlavor += `-${ubiVersionTag}`; if (flags.ironbank) imageFlavor += '-ironbank'; - if (build.isOss()) imageFlavor += '-oss'; // General docker var config - const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; + const license = 'Elastic License'; const imageTag = 'docker.elastic.co/kibana/kibana'; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; - const artifactFlavor = build.isOss() ? '-oss' : ''; - const artifactPrefix = `kibana${artifactFlavor}-${version}-linux`; + const artifactPrefix = `kibana-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); // That would produce oss, default and default-ubi7 - const dockerBuildDir = config.resolveFromRepo( - 'build', - 'kibana-docker', - build.isOss() ? `oss` : `default${imageFlavor}` - ); + const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index 933b3e411b2864..b732e4c80ea370 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -28,11 +28,7 @@ export async function runFpm( const fromBuild = (...paths: string[]) => build.resolvePathForPlatform(linux, ...paths); const pickLicense = () => { - if (build.isOss()) { - return type === 'rpm' ? 'ASL 2.0' : 'ASL-2.0'; - } else { - return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; - } + return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; }; const envFolder = type === 'rpm' ? 'sysconfig' : 'default'; @@ -57,7 +53,7 @@ export async function runFpm( // general info about the package '--name', - build.isOss() ? 'kibana-oss' : 'kibana', + 'kibana', '--description', 'Explore and visualize your Elasticsearch data', '--version', @@ -71,10 +67,6 @@ export async function runFpm( '--license', pickLicense(), - // prevent installing kibana if installing kibana-oss and vice versa - '--conflicts', - build.isOss() ? 'kibana' : 'kibana-oss', - // define install/uninstall scripts '--after-install', resolve(__dirname, 'package_scripts/post_install.sh'), diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index e0f0432c614636..6fc0841551fad8 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -17,6 +17,7 @@ export const storybookAliases = { dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', data_enhanced: 'x-pack/plugins/data_enhanced/.storybook', embeddable: 'src/plugins/embeddable/.storybook', + expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', infra: 'x-pack/plugins/infra/.storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook', diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index 1424cb1c7126fb..7b16c42f4c9b91 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -7,9 +7,11 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginInitializerContext } from '../../../core/server'; +import { ConfigDeprecationProvider, PluginInitializerContext } from '../../../core/server'; import { APMOSSPlugin } from './plugin'; +const deprecations: ConfigDeprecationProvider = ({ unused }) => [unused('fleetMode')]; + export const config = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -22,6 +24,7 @@ export const config = { indexPattern: schema.string({ defaultValue: 'apm-*' }), fleetMode: schema.boolean({ defaultValue: true }), }), + deprecations, }; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/dashboard/public/application/test_helpers/index.ts b/src/plugins/dashboard/public/application/test_helpers/index.ts index d26eadec8f1c9c..7c8ae86074a46d 100644 --- a/src/plugins/dashboard/public/application/test_helpers/index.ts +++ b/src/plugins/dashboard/public/application/test_helpers/index.ts @@ -9,3 +9,4 @@ export { getSampleDashboardInput, getSampleDashboardPanel } from './get_sample_dashboard_input'; export { getSavedDashboardMock } from './get_saved_dashboard_mock'; export { makeDefaultServices } from './make_default_services'; +export { setupIntersectionObserverMock } from './intersection_observer_mock'; diff --git a/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts b/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts new file mode 100644 index 00000000000000..401ec5acdee4ee --- /dev/null +++ b/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/** + * Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely + * on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`. + * + * @param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty` + * overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only + * mock the intersection observer, but its methods. + */ +export function setupIntersectionObserverMock({ + root = null, + rootMargin = '', + thresholds = [], + disconnect = () => null, + observe = () => null, + takeRecords = () => [], + unobserve = () => null, +} = {}): void { + class MockIntersectionObserver implements IntersectionObserver { + readonly root: Element | null = root; + readonly rootMargin: string = rootMargin; + readonly thresholds: readonly number[] = thresholds; + disconnect: () => void = disconnect; + observe: (target: Element) => void = observe; + takeRecords: () => IntersectionObserverEntry[] = takeRecords; + unobserve: (target: Element) => void = unobserve; + } + + Object.defineProperty(window, 'IntersectionObserver', { + writable: true, + configurable: true, + value: MockIntersectionObserver, + }); + + Object.defineProperty(global, 'IntersectionObserver', { + writable: true, + configurable: true, + value: MockIntersectionObserver, + }); +} diff --git a/src/plugins/dashboard/server/ui_settings.ts b/src/plugins/dashboard/server/ui_settings.ts index 34cfff0e4ef473..99eb29a27deaa8 100644 --- a/src/plugins/dashboard/server/ui_settings.ts +++ b/src/plugins/dashboard/server/ui_settings.ts @@ -20,7 +20,7 @@ export const getUISettings = (): Record> => ({ name: i18n.translate('dashboard.labs.enableUI', { defaultMessage: 'Enable labs button in Dashboard', }), - description: i18n.translate('dashboard.labs.enableUnifiedToolbarProjectDescription', { + description: i18n.translate('dashboard.labs.enableLabsDescription', { defaultMessage: 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Dashboard.', }), diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts index b376436756092d..714ae5f7b274bb 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { IFieldType } from '../../index_patterns'; import { Filter } from '../filters'; import { IndexPatternBase } from './types'; @@ -26,5 +25,5 @@ export function filterMatchesIndex(filter: Filter, indexPattern?: IndexPatternBa return filter.meta.index === indexPattern.id; } - return indexPattern.fields.some((field: IFieldType) => field.name === filter.meta.key); + return indexPattern.fields.some((field) => field.name === filter.meta.key); } diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts index d312d034df5641..24852ebf33bda3 100644 --- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts @@ -10,7 +10,6 @@ import { handleNestedFilter } from './handle_nested_filter'; import { fields } from '../../index_patterns/mocks'; import { buildPhraseFilter, buildQueryFilter } from '../filters'; import { IndexPatternBase } from './types'; -import { IFieldType } from '../../index_patterns'; describe('handleNestedFilter', function () { const indexPattern: IndexPatternBase = { @@ -45,8 +44,12 @@ describe('handleNestedFilter', function () { }); it('should return filter untouched if it does not target a field from the given index pattern', () => { - const field = { ...getField('extension'), name: 'notarealfield' }; - const filter = buildPhraseFilter(field as IFieldType, 'jpg', indexPattern); + const field = getField('extension'); + const unrealField = { + ...field!, + name: 'notarealfield', + }; + const filter = buildPhraseFilter(unrealField, 'jpg', indexPattern); const result = handleNestedFilter(filter, indexPattern); expect(result).toBe(filter); }); diff --git a/src/plugins/data/common/es_query/es_query/index.ts b/src/plugins/data/common/es_query/es_query/index.ts index c10ea5846ae3fd..ecc7c8ba5a9f5a 100644 --- a/src/plugins/data/common/es_query/es_query/index.ts +++ b/src/plugins/data/common/es_query/es_query/index.ts @@ -11,4 +11,4 @@ export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; export { decorateQuery } from './decorate_query'; export { getEsQueryConfig } from './get_es_query_config'; -export { IndexPatternBase } from './types'; +export { IndexPatternBase, IndexPatternFieldBase, IFieldSubType } from './types'; diff --git a/src/plugins/data/common/es_query/es_query/types.ts b/src/plugins/data/common/es_query/es_query/types.ts index 21337365160491..9282072cd444d4 100644 --- a/src/plugins/data/common/es_query/es_query/types.ts +++ b/src/plugins/data/common/es_query/es_query/types.ts @@ -6,9 +6,32 @@ * Side Public License, v 1. */ -import { IFieldType } from '../../index_patterns'; +import type { estypes } from '@elastic/elasticsearch'; + +export interface IFieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} +export interface IndexPatternFieldBase { + name: string; + /** + * Kibana field type + */ + type: string; + subType?: IFieldSubType; + /** + * Scripted field painless script + */ + script?: string; + /** + * Scripted field langauge + * Painless is the only valid scripted field language + */ + lang?: estypes.ScriptLanguage; + scripted?: boolean; +} export interface IndexPatternBase { - fields: IFieldType[]; + fields: IndexPatternFieldBase[]; id?: string; } diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/src/plugins/data/common/es_query/filters/build_filters.ts index 369f9530fb92b2..1d8d67b6e937fa 100644 --- a/src/plugins/data/common/es_query/filters/build_filters.ts +++ b/src/plugins/data/common/es_query/filters/build_filters.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { IFieldType, IndexPatternBase } from '../..'; +import { IndexPatternFieldBase, IndexPatternBase } from '../..'; + import { Filter, FILTERS, @@ -20,7 +21,7 @@ import { export function buildFilter( indexPattern: IndexPatternBase, - field: IFieldType, + field: IndexPatternFieldBase, type: FILTERS, negate: boolean, disabled: boolean, @@ -60,7 +61,7 @@ export function buildCustomFilter( function buildBaseFilter( indexPattern: IndexPatternBase, - field: IFieldType, + field: IndexPatternFieldBase, type: FILTERS, params: any ): Filter { diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts index 4836950c3bb277..7a09adb7d9ed6f 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -7,8 +7,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { IFieldType } from '../../index_patterns'; -import { IndexPatternBase } from '..'; +import { IndexPatternFieldBase, IndexPatternBase } from '..'; export type ExistsFilterMeta = FilterMeta; @@ -27,7 +26,7 @@ export const getExistsFilterField = (filter: ExistsFilter) => { return filter.exists && filter.exists.field; }; -export const buildExistsFilter = (field: IFieldType, indexPattern: IndexPatternBase) => { +export const buildExistsFilter = (field: IndexPatternFieldBase, indexPattern: IndexPatternBase) => { return { meta: { index: indexPattern.id, diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts index 27c1e85562097c..68ad16cb31d429 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts @@ -8,8 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import { get, isPlainObject } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { IFieldType } from '../../index_patterns'; -import { IndexPatternBase } from '..'; +import { IndexPatternFieldBase, IndexPatternBase } from '..'; export type PhraseFilterMeta = FilterMeta & { params?: { @@ -59,7 +58,7 @@ export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue => }; export const buildPhraseFilter = ( - field: IFieldType, + field: IndexPatternFieldBase, value: any, indexPattern: IndexPatternBase ): PhraseFilter => { @@ -82,7 +81,7 @@ export const buildPhraseFilter = ( } }; -export const getPhraseScript = (field: IFieldType, value: string) => { +export const getPhraseScript = (field: IndexPatternFieldBase, value: string) => { const convertedValue = getConvertedValueForField(field, value); const script = buildInlineScriptForPhraseFilter(field); @@ -106,7 +105,7 @@ export const getPhraseScript = (field: IFieldType, value: string) => { * https://github.com/elastic/elasticsearch/issues/20941 * https://github.com/elastic/elasticsearch/pull/22201 **/ -export const getConvertedValueForField = (field: IFieldType, value: any) => { +export const getConvertedValueForField = (field: IndexPatternFieldBase, value: any) => { if (typeof value !== 'boolean' && field.type === 'boolean') { if ([1, 'true'].includes(value)) { return true; diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index 2694461fc19300..7f7831e1c7978e 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -9,8 +9,7 @@ import { Filter, FilterMeta } from './meta_filter'; import { getPhraseScript } from './phrase_filter'; import { FILTERS } from './index'; -import { IFieldType } from '../../index_patterns'; -import { IndexPatternBase } from '../es_query'; +import { IndexPatternFieldBase, IndexPatternBase } from '../es_query'; export type PhrasesFilterMeta = FilterMeta & { params: string[]; // The unformatted values @@ -33,7 +32,7 @@ export const getPhrasesFilterField = (filter: PhrasesFilter) => { // Creates a filter where the given field matches one or more of the given values // params should be an array of values export const buildPhrasesFilter = ( - field: IFieldType, + field: IndexPatternFieldBase, params: any[], indexPattern: IndexPatternBase ) => { diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index bb7ecc09ebc349..30e52b21d52b75 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -9,15 +9,15 @@ import { each } from 'lodash'; import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter'; import { fields, getField } from '../../index_patterns/mocks'; -import { IIndexPattern, IFieldType } from '../../index_patterns'; +import { IndexPatternBase, IndexPatternFieldBase } from '../es_query'; describe('Range filter builder', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { indexPattern = { id: 'id', - } as IIndexPattern; + } as IndexPatternBase; }); it('should be a function', () => { @@ -130,7 +130,7 @@ describe('Range filter builder', () => { }); describe('when given params where one side is infinite', () => { - let field: IFieldType; + let field: IndexPatternFieldBase; let filter: RangeFilter; beforeEach(() => { @@ -160,7 +160,7 @@ describe('Range filter builder', () => { }); describe('when given params where both sides are infinite', () => { - let field: IFieldType; + let field: IndexPatternFieldBase; let filter: RangeFilter; beforeEach(() => { @@ -186,9 +186,9 @@ describe('Range filter builder', () => { }); describe('getRangeFilterField', function () { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = ({ fields, - } as unknown) as IIndexPattern; + } as unknown) as IndexPatternBase; test('should return the name of the field a range query is targeting', () => { const field = indexPattern.fields.find((patternField) => patternField.name === 'bytes'); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index 9f1d9a5d089264..e44e23f64936e1 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -8,8 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import { map, reduce, mapValues, get, keys, pickBy } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { IFieldType } from '../../index_patterns'; -import { IndexPatternBase } from '..'; +import { IndexPatternBase, IndexPatternFieldBase } from '..'; const OPERANDS_IN_RANGE = 2; @@ -83,13 +82,13 @@ export const getRangeFilterField = (filter: RangeFilter) => { return filter.range && Object.keys(filter.range)[0]; }; -const formatValue = (field: IFieldType, params: any[]) => +const formatValue = (params: any[]) => map(params, (val: any, key: string) => get(operators, key) + val).join(' '); // Creates a filter where the value for the given field is in the given range // params should be an object containing `lt`, `lte`, `gt`, and/or `gte` export const buildRangeFilter = ( - field: IFieldType, + field: IndexPatternFieldBase, params: RangeFilterParams, indexPattern: IndexPatternBase, formattedValue?: string @@ -124,7 +123,7 @@ export const buildRangeFilter = ( filter.meta.field = field.name; } else if (field.scripted) { filter.script = getRangeScript(field, params); - filter.script.script.params.value = formatValue(field, filter.script.script.params); + filter.script.script.params.value = formatValue(filter.script.script.params); filter.meta.field = field.name; } else { @@ -135,7 +134,7 @@ export const buildRangeFilter = ( return filter as RangeFilter; }; -export const getRangeScript = (field: IFieldType, params: RangeFilterParams) => { +export const getRangeScript = (field: IndexPatternFieldBase, params: RangeFilterParams) => { const knownParams = mapValues( pickBy(params, (val, key: any) => key in operators), (value) => (field.type === 'number' && typeof value === 'string' ? parseFloat(value) : value) diff --git a/src/plugins/data/common/es_query/kuery/functions/exists.ts b/src/plugins/data/common/es_query/kuery/functions/exists.ts index fa6c37e6ba18f7..4df566d874d8b5 100644 --- a/src/plugins/data/common/es_query/kuery/functions/exists.ts +++ b/src/plugins/data/common/es_query/kuery/functions/exists.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { get } from 'lodash'; import * as literal from '../node_types/literal'; -import { KueryNode, IFieldType, IndexPatternBase } from '../../..'; +import { KueryNode, IndexPatternFieldBase, IndexPatternBase } from '../../..'; export function buildNodeParams(fieldName: string) { return { @@ -30,9 +29,9 @@ export function toElasticsearchQuery( value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value, }; const fieldName = literal.toElasticsearchQuery(fullFieldNameArg); - const field = get(indexPattern, 'fields', []).find((fld: IFieldType) => fld.name === fieldName); + const field = indexPattern?.fields?.find((fld: IndexPatternFieldBase) => fld.name === fieldName); - if (field && (field as IFieldType).scripted) { + if (field?.scripted) { throw new Error(`Exists query does not support scripted fields`); } return { diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts index 38a433b1b80ab0..79bef10b14f71f 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts +++ b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..'; +import { IndexPatternBase, KueryNode, LatLon } from '../../..'; export function buildNodeParams(fieldName: string, params: any) { params = _.pick(params, 'topLeft', 'bottomRight'); @@ -36,8 +36,8 @@ export function toElasticsearchQuery( value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value, }; const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string; - const fieldList: IFieldType[] = indexPattern?.fields ?? []; - const field = fieldList.find((fld: IFieldType) => fld.name === fieldName); + const fieldList = indexPattern?.fields ?? []; + const field = fieldList.find((fld) => fld.name === fieldName); const queryParams = args.reduce((acc: any, arg: any) => { const snakeArgName = _.snakeCase(arg.name); diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts index 69de7248a7b380..2e3280138502a7 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts +++ b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts @@ -8,7 +8,7 @@ import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..'; +import { IndexPatternBase, KueryNode, LatLon } from '../../..'; import { LiteralTypeBuildNode } from '../node_types/types'; export function buildNodeParams(fieldName: string, points: LatLon[]) { @@ -35,8 +35,8 @@ export function toElasticsearchQuery( value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value, }; const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string; - const fieldList: IFieldType[] = indexPattern?.fields ?? []; - const field = fieldList.find((fld: IFieldType) => fld.name === fieldName); + const fieldList = indexPattern?.fields ?? []; + const field = fieldList.find((fld) => fld.name === fieldName); const queryParams = { points: points.map((point: LiteralTypeBuildNode) => { return ast.toElasticsearchQuery(point, indexPattern, config, context); diff --git a/src/plugins/data/common/es_query/kuery/functions/is.ts b/src/plugins/data/common/es_query/kuery/functions/is.ts index 55d036c2156f9b..381913670c26aa 100644 --- a/src/plugins/data/common/es_query/kuery/functions/is.ts +++ b/src/plugins/data/common/es_query/kuery/functions/is.ts @@ -11,7 +11,7 @@ import { getPhraseScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; -import { IndexPatternBase, KueryNode, IFieldType } from '../../..'; +import { IndexPatternBase, KueryNode, IndexPatternFieldBase } from '../../..'; import * as ast from '../ast'; @@ -100,7 +100,7 @@ export function toElasticsearchQuery( return { match_all: {} }; } - const queries = fields!.reduce((accumulator: any, field: IFieldType) => { + const queries = fields!.reduce((accumulator: any, field: IndexPatternFieldBase) => { const wrapWithNestedQuery = (query: any) => { // Wildcards can easily include nested and non-nested fields. There isn't a good way to let // users handle this themselves so we automatically add nested queries in this scenario. diff --git a/src/plugins/data/common/es_query/kuery/functions/range.ts b/src/plugins/data/common/es_query/kuery/functions/range.ts index caefa7e5373cac..b134434dc182b6 100644 --- a/src/plugins/data/common/es_query/kuery/functions/range.ts +++ b/src/plugins/data/common/es_query/kuery/functions/range.ts @@ -13,7 +13,7 @@ import { getRangeScript, RangeFilterParams } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; -import { IndexPatternBase, KueryNode, IFieldType } from '../../..'; +import { IndexPatternBase, KueryNode } from '../../..'; export function buildNodeParams(fieldName: string, params: RangeFilterParams) { const paramsToMap = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format'); @@ -62,7 +62,7 @@ export function toElasticsearchQuery( }); } - const queries = fields!.map((field: IFieldType) => { + const queries = fields!.map((field) => { const wrapWithNestedQuery = (query: any) => { // Wildcards can easily include nested and non-nested fields. There isn't a good way to let // users handle this themselves so we automatically add nested queries in this scenario. diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts index 47fe677454cbfc..949f94d0435539 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts @@ -6,21 +6,21 @@ * Side Public License, v 1. */ +import { IndexPatternBase } from '../../..'; import { fields } from '../../../../index_patterns/mocks'; import { nodeTypes } from '../../index'; -import { IIndexPattern, IFieldType } from '../../../../index_patterns'; // @ts-ignore import { getFields } from './get_fields'; describe('getFields', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { indexPattern = ({ fields, - } as unknown) as IIndexPattern; + } as unknown) as IndexPatternBase; }); describe('field names without a wildcard', () => { @@ -41,14 +41,14 @@ describe('getFields', () => { }); test('should not match a wildcard in a literal node', () => { - const indexPatternWithWildField = { + const indexPatternWithWildField: IndexPatternBase = ({ title: 'wildIndex', fields: [ { name: 'foo*', }, ], - } as IIndexPattern; + } as unknown) as IndexPatternBase; const fieldNameNode = nodeTypes.literal.buildNode('foo*'); const results = getFields(fieldNameNode, indexPatternWithWildField); @@ -76,8 +76,8 @@ describe('getFields', () => { expect(Array.isArray(results)).toBeTruthy(); expect(results).toHaveLength(2); - expect(results!.find((field: IFieldType) => field.name === 'machine.os')).toBeDefined(); - expect(results!.find((field: IFieldType) => field.name === 'machine.os.raw')).toBeDefined(); + expect(results!.find((field) => field.name === 'machine.os')).toBeDefined(); + expect(results!.find((field) => field.name === 'machine.os.raw')).toBeDefined(); }); }); }); diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts index 644791637aa709..2a31ebeee2fab7 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts @@ -7,7 +7,7 @@ */ import { getFields } from './get_fields'; -import { IndexPatternBase, IFieldType, KueryNode } from '../../../..'; +import { IndexPatternBase, IndexPatternFieldBase, KueryNode } from '../../../..'; export function getFullFieldNameNode( rootNameNode: any, @@ -27,7 +27,7 @@ export function getFullFieldNameNode( } const fields = getFields(fullFieldNameNode, indexPattern); - const errors = fields!.reduce((acc: any, field: IFieldType) => { + const errors = fields!.reduce((acc: any, field: IndexPatternFieldBase) => { const nestedPathFromField = field.subType && field.subType.nested ? field.subType.nested.path : undefined; diff --git a/src/plugins/data/common/field_formats/converters/string.test.ts b/src/plugins/data/common/field_formats/converters/string.test.ts index ccb7a58285b207..d691712b674ddd 100644 --- a/src/plugins/data/common/field_formats/converters/string.test.ts +++ b/src/plugins/data/common/field_formats/converters/string.test.ts @@ -8,6 +8,14 @@ import { StringFormat } from './string'; +/** + * Removes a wrapping span, that is created by the field formatter infrastructure + * and we're not caring about in these tests. + */ +function stripSpan(input: string): string { + return input.replace(/^\(.*)\<\/span\>$/, '$1'); +} + describe('String Format', () => { test('convert a string to lower case', () => { const string = new StringFormat( @@ -17,6 +25,7 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('Kibana')).toBe('kibana'); + expect(stripSpan(string.convert('Kibana', 'html'))).toBe('kibana'); }); test('convert a string to upper case', () => { @@ -27,6 +36,7 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('Kibana')).toBe('KIBANA'); + expect(stripSpan(string.convert('Kibana', 'html'))).toBe('KIBANA'); }); test('decode a base64 string', () => { @@ -37,6 +47,7 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('Zm9vYmFy')).toBe('foobar'); + expect(stripSpan(string.convert('Zm9vYmFy', 'html'))).toBe('foobar'); }); test('convert a string to title case', () => { @@ -47,10 +58,15 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('PLEASE DO NOT SHOUT')).toBe('Please Do Not Shout'); + expect(stripSpan(string.convert('PLEASE DO NOT SHOUT', 'html'))).toBe('Please Do Not Shout'); expect(string.convert('Mean, variance and standard_deviation.')).toBe( 'Mean, Variance And Standard_deviation.' ); + expect(stripSpan(string.convert('Mean, variance and standard_deviation.', 'html'))).toBe( + 'Mean, Variance And Standard_deviation.' + ); expect(string.convert('Stay CALM!')).toBe('Stay Calm!'); + expect(stripSpan(string.convert('Stay CALM!', 'html'))).toBe('Stay Calm!'); }); test('convert a string to short case', () => { @@ -61,6 +77,7 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('dot.notated.string')).toBe('d.n.string'); + expect(stripSpan(string.convert('dot.notated.string', 'html'))).toBe('d.n.string'); }); test('convert a string to unknown transform case', () => { @@ -82,5 +99,16 @@ describe('String Format', () => { jest.fn() ); expect(string.convert('%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98')).toBe('안녕 키바나'); + expect( + stripSpan(string.convert('%EC%95%88%EB%85%95%20%ED%82%A4%EB%B0%94%EB%82%98', 'html')) + ).toBe('안녕 키바나'); + }); + + test('outputs specific empty value', () => { + const string = new StringFormat(); + expect(string.convert('')).toBe('(empty)'); + expect(stripSpan(string.convert('', 'html'))).toBe( + '(empty)' + ); }); }); diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index 64367df5d90dda..28dd714abaf411 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -6,14 +6,15 @@ * Side Public License, v 1. */ +import escape from 'lodash/escape'; import { i18n } from '@kbn/i18n'; -import { asPrettyString } from '../utils'; +import { asPrettyString, getHighlightHtml } from '../utils'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; -import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; +import { TextContextTypeConvert, FIELD_FORMAT_IDS, HtmlContextTypeConvert } from '../types'; import { shortenDottedString } from '../../utils'; -export const emptyLabel = i18n.translate('data.fieldFormats.string.emptyLabel', { +const emptyLabel = i18n.translate('data.fieldFormats.string.emptyLabel', { defaultMessage: '(empty)', }); @@ -127,4 +128,14 @@ export class StringFormat extends FieldFormat { return asPrettyString(val); } }; + + htmlConvert: HtmlContextTypeConvert = (val, { hit, field } = {}) => { + if (val === '') { + return `${emptyLabel}`; + } + + return hit?.highlight?.[field?.name] + ? getHighlightHtml(val, hit.highlight[field.name]) + : escape(this.textConvert(val)); + }; } diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 0fb7a46c2cf73d..3b2e25d3d80a6c 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -5,18 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { estypes } from '@elastic/elasticsearch'; -import { FieldSpec, IFieldSubType, IndexPattern } from '../..'; +import { IndexPatternFieldBase, FieldSpec, IndexPattern } from '../..'; /** * @deprecated * Use IndexPatternField or FieldSpec instead */ -export interface IFieldType { - name: string; - type: string; - script?: string; - lang?: estypes.ScriptLanguage; +export interface IFieldType extends IndexPatternFieldBase { count?: number; // esTypes might be undefined on old index patterns that have not been refreshed since we added // this prop. It is also undefined on scripted fields. @@ -27,8 +22,6 @@ export interface IFieldType { sortable?: boolean; visualizable?: boolean; readFromDocValues?: boolean; - scripted?: boolean; - subType?: IFieldSubType; displayName?: string; customLabel?: string; format?: any; diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index a88f029c0c7cd9..b03e745df74a6a 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -9,7 +9,7 @@ import type { estypes } from '@elastic/elasticsearch'; import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications'; // eslint-disable-next-line import type { SavedObject } from 'src/core/server'; -import type { IndexPatternBase } from '../es_query'; +import type { IndexPatternFieldBase, IFieldSubType, IndexPatternBase } from '../es_query'; import { IFieldType } from './fields'; import { RUNTIME_FIELD_TYPES } from './constants'; import { SerializedFieldFormat } from '../../../expressions/common'; @@ -32,6 +32,7 @@ export interface RuntimeField { */ export interface IIndexPattern extends IndexPatternBase { title: string; + fields: IFieldType[]; /** * Type is used for identifying rollup indices, otherwise left undefined */ @@ -149,12 +150,6 @@ export type AggregationRestrictions = Record< time_zone?: string; } >; - -export interface IFieldSubType { - multi?: { parent: string }; - nested?: { path: string }; -} - export interface TypeMeta { aggs?: Record; [key: string]: any; @@ -183,30 +178,17 @@ export interface FieldSpecExportFmt { /** * Serialized version of IndexPatternField */ -export interface FieldSpec { +export interface FieldSpec extends IndexPatternFieldBase { /** * Popularity count is used by discover */ count?: number; - /** - * Scripted field painless script - */ - script?: string; - /** - * Scripted field langauge - * Painless is the only valid scripted field language - */ - lang?: estypes.ScriptLanguage; conflictDescriptions?: Record; format?: SerializedFieldFormat; - name: string; - type: string; esTypes?: string[]; - scripted?: boolean; searchable: boolean; aggregatable: boolean; readFromDocValues?: boolean; - subType?: IFieldSubType; indexed?: boolean; customLabel?: string; runtimeField?: RuntimeField; diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts index 1b7bfbc09ad162..a2b5a568b70ef3 100644 --- a/src/plugins/data/config.ts +++ b/src/plugins/data/config.ts @@ -15,6 +15,21 @@ export const configSchema = schema.object({ }), valueSuggestions: schema.object({ enabled: schema.boolean({ defaultValue: true }), + method: schema.oneOf([schema.literal('terms_enum'), schema.literal('terms_agg')], { + defaultValue: 'terms_enum', + }), + tiers: schema.arrayOf( + schema.oneOf([ + schema.literal('data_content'), + schema.literal('data_hot'), + schema.literal('data_warm'), + schema.literal('data_cold'), + schema.literal('data_frozen'), + ]), + { + defaultValue: ['data_hot', 'data_warm', 'data_content', 'data_cold'], + } + ), terminateAfter: schema.duration({ defaultValue: 100000 }), timeout: schema.duration({ defaultValue: 1000 }), }), diff --git a/src/plugins/data/public/field_formats/converters/_index.scss b/src/plugins/data/public/field_formats/converters/_index.scss new file mode 100644 index 00000000000000..cc13062a3ef8bc --- /dev/null +++ b/src/plugins/data/public/field_formats/converters/_index.scss @@ -0,0 +1 @@ +@import './string'; diff --git a/src/plugins/data/public/field_formats/converters/_string.scss b/src/plugins/data/public/field_formats/converters/_string.scss new file mode 100644 index 00000000000000..9d97f0195780c1 --- /dev/null +++ b/src/plugins/data/public/field_formats/converters/_string.scss @@ -0,0 +1,3 @@ +.ffString__emptyValue { + color: $euiColorDarkShade; +} diff --git a/src/plugins/data/public/index.scss b/src/plugins/data/public/index.scss index 467efa98934eca..c0eebf3402771d 100644 --- a/src/plugins/data/public/index.scss +++ b/src/plugins/data/public/index.scss @@ -1,2 +1,3 @@ @import './ui/index'; @import './utils/table_inspector_view/index'; +@import './field_formats/converters/index'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 6a49fab0e33ff4..35094fac1cc0f1 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -808,11 +808,11 @@ export const esFilters: { FILTERS: typeof FILTERS; FilterStateStore: typeof FilterStateStore; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; - buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; + buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; + buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; + buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; - buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; + buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter; isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter; isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter; @@ -1242,10 +1242,11 @@ export interface IFieldSubType { }; } +// Warning: (ae-forgotten-export) The symbol "IndexPatternFieldBase" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IFieldType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @deprecated (undocumented) -export interface IFieldType { +export interface IFieldType extends IndexPatternFieldBase { // (undocumented) aggregatable?: boolean; // (undocumented) @@ -1261,28 +1262,16 @@ export interface IFieldType { // (undocumented) format?: any; // (undocumented) - lang?: estypes.ScriptLanguage; - // (undocumented) - name: string; - // (undocumented) readFromDocValues?: boolean; // (undocumented) - script?: string; - // (undocumented) - scripted?: boolean; - // (undocumented) searchable?: boolean; // (undocumented) sortable?: boolean; // (undocumented) - subType?: IFieldSubType; - // (undocumented) toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField']; }) => FieldSpec; // (undocumented) - type: string; - // (undocumented) visualizable?: boolean; } @@ -1295,6 +1284,8 @@ export interface IIndexPattern extends IndexPatternBase { // // (undocumented) fieldFormatMap?: Record | undefined>; + // (undocumented) + fields: IFieldType[]; getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat; // (undocumented) getTimeField?(): IFieldType | undefined; @@ -1394,7 +1385,9 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getAggregationRestrictions(): Record; +let esClientMock: DeeplyMockedKeys; +const configMock = ({ + autocomplete: { + valueSuggestions: { timeout: duration(4513), terminateAfter: duration(98430) }, + }, +} as unknown) as ConfigSchema; +const mockResponse = { + body: { + aggregations: { + suggestions: { + buckets: [{ key: 'whoa' }, { key: 'amazing' }], + }, + }, + }, +} as ApiResponse>; + +describe('terms agg suggestions', () => { + beforeEach(() => { + const requestHandlerContext = coreMock.createRequestHandlerContext(); + savedObjectsClientMock = requestHandlerContext.savedObjects.client; + esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; + esClientMock.search.mockResolvedValue(mockResponse); + }); + + it('calls the _search API with a terms agg with the given args', async () => { + const result = await termsAggSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [], + { name: 'field_name', type: 'string' } + ); + + const [[args]] = esClientMock.search.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "body": Object { + "aggs": Object { + "suggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "field_name", + "include": "query.*", + "shard_size": 10, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [], + }, + }, + "size": 0, + "terminate_after": 98430, + "timeout": "4513ms", + }, + "index": "index", + } + `); + expect(result).toMatchInlineSnapshot(` + Array [ + "whoa", + "amazing", + ] + `); + }); +}); diff --git a/src/plugins/data/server/autocomplete/terms_agg.ts b/src/plugins/data/server/autocomplete/terms_agg.ts new file mode 100644 index 00000000000000..b902bae49898fc --- /dev/null +++ b/src/plugins/data/server/autocomplete/terms_agg.ts @@ -0,0 +1,106 @@ +/* + * 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 { get, map } from 'lodash'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import { estypes } from '@elastic/elasticsearch'; +import { ConfigSchema } from '../../config'; +import { IFieldType } from '../../common'; +import { findIndexPatternById, getFieldByName } from '../index_patterns'; +import { shimAbortSignal } from '../search'; + +export async function termsAggSuggestions( + config: ConfigSchema, + savedObjectsClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + index: string, + fieldName: string, + query: string, + filters?: estypes.QueryDslQueryContainer[], + field?: IFieldType, + abortSignal?: AbortSignal +) { + const autocompleteSearchOptions = { + timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`, + terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(), + }; + + if (!field?.name && !field?.type) { + const indexPattern = await findIndexPatternById(savedObjectsClient, index); + + field = indexPattern && getFieldByName(fieldName, indexPattern); + } + + const body = await getBody(autocompleteSearchOptions, field ?? fieldName, query, filters); + + const promise = esClient.search({ index, body }); + const result = await shimAbortSignal(promise, abortSignal); + + const buckets = + get(result.body, 'aggregations.suggestions.buckets') || + get(result.body, 'aggregations.nestedSuggestions.suggestions.buckets'); + + return map(buckets ?? [], 'key'); +} + +async function getBody( + // eslint-disable-next-line @typescript-eslint/naming-convention + { timeout, terminate_after }: Record, + field: IFieldType | string, + query: string, + filters: estypes.QueryDslQueryContainer[] = [] +) { + const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name); + + // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators + const getEscapedQuery = (q: string = '') => + q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); + + // Helps ensure that the regex is not evaluated eagerly against the terms dictionary + const executionHint = 'map' as const; + + // We don't care about the accuracy of the counts, just the content of the terms, so this reduces + // the amount of information that needs to be transmitted to the coordinating node + const shardSize = 10; + const body = { + size: 0, + timeout, + terminate_after, + query: { + bool: { + filter: filters, + }, + }, + aggs: { + suggestions: { + terms: { + field: isFieldObject(field) ? field.name : field, + include: `${getEscapedQuery(query)}.*`, + execution_hint: executionHint, + shard_size: shardSize, + }, + }, + }, + }; + + if (isFieldObject(field) && field.subType && field.subType.nested) { + return { + ...body, + aggs: { + nestedSuggestions: { + nested: { + path: field.subType.nested.path, + }, + aggs: body.aggs, + }, + }, + }; + } + + return body; +} diff --git a/src/plugins/data/server/autocomplete/terms_enum.test.ts b/src/plugins/data/server/autocomplete/terms_enum.test.ts new file mode 100644 index 00000000000000..be8f179db29c05 --- /dev/null +++ b/src/plugins/data/server/autocomplete/terms_enum.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { termsEnumSuggestions } from './terms_enum'; +import { coreMock } from '../../../../core/server/mocks'; +import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import { ConfigSchema } from '../../config'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { ApiResponse } from '@elastic/elasticsearch'; + +let savedObjectsClientMock: jest.Mocked; +let esClientMock: DeeplyMockedKeys; +const configMock = { + autocomplete: { valueSuggestions: { tiers: ['data_hot', 'data_warm', 'data_content'] } }, +} as ConfigSchema; +const mockResponse = { + body: { terms: ['whoa', 'amazing'] }, +}; + +describe('_terms_enum suggestions', () => { + beforeEach(() => { + const requestHandlerContext = coreMock.createRequestHandlerContext(); + savedObjectsClientMock = requestHandlerContext.savedObjects.client; + esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; + esClientMock.transport.request.mockResolvedValue((mockResponse as unknown) as ApiResponse); + }); + + it('calls the _terms_enum API with the field, query, filters, and config tiers', async () => { + const result = await termsEnumSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [], + { name: 'field_name', type: 'string' } + ); + + const [[args]] = esClientMock.transport.request.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "body": Object { + "field": "field_name", + "index_filter": Object { + "bool": Object { + "must": Array [ + Object { + "terms": Object { + "_tier": Array [ + "data_hot", + "data_warm", + "data_content", + ], + }, + }, + ], + }, + }, + "string": "query", + }, + "method": "POST", + "path": "/index/_terms_enum", + } + `); + expect(result).toEqual(mockResponse.body.terms); + }); +}); diff --git a/src/plugins/data/server/autocomplete/terms_enum.ts b/src/plugins/data/server/autocomplete/terms_enum.ts new file mode 100644 index 00000000000000..c2452b0a099d04 --- /dev/null +++ b/src/plugins/data/server/autocomplete/terms_enum.ts @@ -0,0 +1,62 @@ +/* + * 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 { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import { estypes } from '@elastic/elasticsearch'; +import { IFieldType } from '../../common'; +import { findIndexPatternById, getFieldByName } from '../index_patterns'; +import { shimAbortSignal } from '../search'; +import { getKbnServerError } from '../../../kibana_utils/server'; +import { ConfigSchema } from '../../config'; + +export async function termsEnumSuggestions( + config: ConfigSchema, + savedObjectsClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + index: string, + fieldName: string, + query: string, + filters?: estypes.QueryDslQueryContainer[], + field?: IFieldType, + abortSignal?: AbortSignal +) { + const { tiers } = config.autocomplete.valueSuggestions; + if (!field?.name && !field?.type) { + const indexPattern = await findIndexPatternById(savedObjectsClient, index); + field = indexPattern && getFieldByName(fieldName, indexPattern); + } + + try { + const promise = esClient.transport.request({ + method: 'POST', + path: encodeURI(`/${index}/_terms_enum`), + body: { + field: field?.name ?? field, + string: query, + index_filter: { + bool: { + must: [ + ...(filters ?? []), + { + terms: { + _tier: tiers, + }, + }, + ], + }, + }, + }, + }); + + const result = await shimAbortSignal(promise, abortSignal); + + return result.body.terms; + } catch (e) { + throw getKbnServerError(e); + } +} diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 8fa14f8cbbd427..bd622d0151c93e 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -6,17 +6,15 @@ * Side Public License, v 1. */ -import { get, map } from 'lodash'; import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; - import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import type { estypes } from '@elastic/elasticsearch'; -import type { IFieldType } from '../index'; -import { findIndexPatternById, getFieldByName } from '../index_patterns'; import { getRequestAbortedSignal } from '../lib'; -import { ConfigSchema } from '../../config'; +import { getKbnServerError } from '../../../kibana_utils/server'; +import type { ConfigSchema } from '../../config'; +import { termsEnumSuggestions } from './terms_enum'; +import { termsAggSuggestions } from './terms_agg'; export function registerValueSuggestionsRoute(router: IRouter, config$: Observable) { router.post( @@ -44,88 +42,28 @@ export function registerValueSuggestionsRoute(router: IRouter, config$: Observab const config = await config$.pipe(first()).toPromise(); const { field: fieldName, query, filters, fieldMeta } = request.body; const { index } = request.params; - const { client } = context.core.elasticsearch.legacy; - const signal = getRequestAbortedSignal(request.events.aborted$); - - const autocompleteSearchOptions = { - timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`, - terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(), - }; - - let field: IFieldType | undefined = fieldMeta; - - if (!field?.name && !field?.type) { - const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); - - field = indexPattern && getFieldByName(fieldName, indexPattern); + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + + try { + const fn = + config.autocomplete.valueSuggestions.method === 'terms_enum' + ? termsEnumSuggestions + : termsAggSuggestions; + const body = await fn( + config, + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser, + index, + fieldName, + query, + filters, + fieldMeta, + abortSignal + ); + return response.ok({ body }); + } catch (e) { + throw getKbnServerError(e); } - - const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); - - const result = await client.callAsCurrentUser('search', { index, body }, { signal }); - - const buckets: any[] = - get(result, 'aggregations.suggestions.buckets') || - get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); - - return response.ok({ body: map(buckets || [], 'key') }); } ); } - -async function getBody( - // eslint-disable-next-line @typescript-eslint/naming-convention - { timeout, terminate_after }: Record, - field: IFieldType | string, - query: string, - filters: estypes.QueryDslQueryContainer[] = [] -) { - const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name); - - // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators - const getEscapedQuery = (q: string = '') => - q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); - - // Helps ensure that the regex is not evaluated eagerly against the terms dictionary - const executionHint = 'map' as const; - - // We don't care about the accuracy of the counts, just the content of the terms, so this reduces - // the amount of information that needs to be transmitted to the coordinating node - const shardSize = 10; - const body = { - size: 0, - timeout, - terminate_after, - query: { - bool: { - filter: filters, - }, - }, - aggs: { - suggestions: { - terms: { - field: isFieldObject(field) ? field.name : field, - include: `${getEscapedQuery(query)}.*`, - execution_hint: executionHint, - shard_size: shardSize, - }, - }, - }, - }; - - if (isFieldObject(field) && field.subType && field.subType.nested) { - return { - ...body, - aggs: { - nestedSuggestions: { - nested: { - path: field.subType.nested.path, - }, - aggs: body.aggs, - }, - }, - }; - } - - return body; -} diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 86aaf64dea8523..c21024d8264621 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -447,11 +447,11 @@ export const esFilters: { buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; buildCustomFilter: typeof buildCustomFilter; buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; + buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; buildFilter: typeof buildFilter; - buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; - buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; + buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; + buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; + buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; isFilterDisabled: (filter: import("../common").Filter) => boolean; }; @@ -693,10 +693,11 @@ export interface IFieldSubType { }; } +// Warning: (ae-forgotten-export) The symbol "IndexPatternFieldBase" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IFieldType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @deprecated (undocumented) -export interface IFieldType { +export interface IFieldType extends IndexPatternFieldBase { // (undocumented) aggregatable?: boolean; // (undocumented) @@ -712,21 +713,11 @@ export interface IFieldType { // (undocumented) format?: any; // (undocumented) - lang?: estypes.ScriptLanguage; - // (undocumented) - name: string; - // (undocumented) readFromDocValues?: boolean; // (undocumented) - script?: string; - // (undocumented) - scripted?: boolean; - // (undocumented) searchable?: boolean; // (undocumented) sortable?: boolean; - // (undocumented) - subType?: IFieldSubType; // Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -734,8 +725,6 @@ export interface IFieldType { getFormatterForField?: IndexPattern['getFormatterForField']; }) => FieldSpec; // (undocumented) - type: string; - // (undocumented) visualizable?: boolean; } @@ -780,7 +769,9 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getAggregationRestrictions(): Record [bucket.key, getAggValue(bucket, metric)]; -} +export const functions = [revealImageFunction]; + +export { revealImageFunction }; diff --git a/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts new file mode 100644 index 00000000000000..633a132fea5e37 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image.test.ts @@ -0,0 +1,168 @@ +/* + * 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 { + functionWrapper, + elasticOutline, + elasticLogo, +} from '../../../presentation_util/common/lib'; +import { getFunctionErrors } from '../i18n'; +import { revealImageFunction } from './reveal_image_function'; +import { Origin } from '../types'; +import { ExecutionContext } from 'src/plugins/expressions'; + +const errors = getFunctionErrors().revealImage; + +describe('revealImageFunction', () => { + const fn = functionWrapper(revealImageFunction); + + it('returns a render as revealImage', () => { + const result = fn( + 0.5, + { + image: null, + emptyImage: null, + origin: Origin.BOTTOM, + }, + {} as ExecutionContext + ); + expect(result).toHaveProperty('type', 'render'); + expect(result).toHaveProperty('as', 'revealImage'); + }); + + describe('context', () => { + it('throws when context is not a number between 0 and 1', () => { + expect(() => { + fn( + 10, + { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.TOP, + }, + {} as ExecutionContext + ); + }).toThrow(new RegExp(errors.invalidPercent(10).message)); + + expect(() => { + fn( + -0.1, + { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.TOP, + }, + {} as ExecutionContext + ); + }).toThrow(new RegExp(errors.invalidPercent(-0.1).message)); + }); + }); + + describe('args', () => { + describe('image', () => { + it('sets the image', () => { + const result = fn( + 0.89, + { + emptyImage: null, + origin: Origin.TOP, + image: elasticLogo, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('image', elasticLogo); + }); + + it('defaults to the Elastic outline logo', () => { + const result = fn( + 0.89, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('image', elasticOutline); + }); + }); + + describe('emptyImage', () => { + it('sets the background image', () => { + const result = fn( + 0, + { + emptyImage: elasticLogo, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('emptyImage', elasticLogo); + }); + + it('sets emptyImage to null', () => { + const result = fn( + 0, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('emptyImage', null); + }); + }); + + describe('origin', () => { + it('sets which side to start the reveal from', () => { + let result = fn( + 1, + { + emptyImage: null, + origin: Origin.TOP, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'top'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.LEFT, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'left'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.BOTTOM, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'bottom'); + result = fn( + 1, + { + emptyImage: null, + origin: Origin.RIGHT, + image: null, + }, + {} as ExecutionContext + ).value; + expect(result).toHaveProperty('origin', 'right'); + }); + }); + }); +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts similarity index 59% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts rename to src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts index 91d70609ab7088..33e61e85f95315 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts +++ b/src/plugins/expression_reveal_image/common/expression_functions/reveal_image_function.ts @@ -1,41 +1,16 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 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 { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; -import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl'; -import { elasticOutline } from '../../lib/elastic_outline'; -import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; +import { resolveWithMissingImage, elasticOutline } from '../../../presentation_util/common/lib'; +import { getFunctionHelp, getFunctionErrors } from '../i18n'; +import { ExpressionRevealImageFunction, Origin } from '../types'; -export enum Origin { - TOP = 'top', - LEFT = 'left', - BOTTOM = 'bottom', - RIGHT = 'right', -} - -interface Arguments { - image: string | null; - emptyImage: string | null; - origin: Origin; -} - -export interface Output { - image: string; - emptyImage: string; - origin: Origin; - percent: number; -} - -export function revealImage(): ExpressionFunctionDefinition< - 'revealImage', - number, - Arguments, - ExpressionValueRender -> { +export const revealImageFunction: ExpressionRevealImageFunction = () => { const { help, args: argHelp } = getFunctionHelp().revealImage; const errors = getFunctionErrors().revealImage; @@ -80,4 +55,4 @@ export function revealImage(): ExpressionFunctionDefinition< }; }, }; -} +}; diff --git a/src/plugins/expression_reveal_image/common/i18n/constants.ts b/src/plugins/expression_reveal_image/common/i18n/constants.ts new file mode 100644 index 00000000000000..413f376515a335 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/constants.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const BASE64 = '`base64`'; +export const URL = 'URL'; diff --git a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts similarity index 52% rename from x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts rename to src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts index 374334824d61ab..ccf9967bd6a65c 100644 --- a/x-pack/plugins/canvas/i18n/functions/dict/reveal_image.ts +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/dict/reveal_image.ts @@ -1,23 +1,21 @@ /* * 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 { i18n } from '@kbn/i18n'; -import { revealImage } from '../../../canvas_plugin_src/functions/common/revealImage'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; import { Position } from '../../../types'; import { BASE64, URL } from '../../constants'; -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.revealImageHelpText', { +export const help = { + help: i18n.translate('expressionRevealImage.functions.revealImageHelpText', { defaultMessage: 'Configures an image reveal element.', }), args: { - image: i18n.translate('xpack.canvas.functions.revealImage.args.imageHelpText', { + image: i18n.translate('expressionRevealImage.functions.revealImage.args.imageHelpText', { defaultMessage: 'The image to reveal. Provide an image asset as a {BASE64} data {URL}, ' + 'or pass in a sub-expression.', @@ -26,16 +24,19 @@ export const help: FunctionHelp> = { URL, }, }), - emptyImage: i18n.translate('xpack.canvas.functions.revealImage.args.emptyImageHelpText', { - defaultMessage: - 'An optional background image to reveal over. ' + - 'Provide an image asset as a `{BASE64}` data {URL}, or pass in a sub-expression.', - values: { - BASE64, - URL, - }, - }), - origin: i18n.translate('xpack.canvas.functions.revealImage.args.originHelpText', { + emptyImage: i18n.translate( + 'expressionRevealImage.functions.revealImage.args.emptyImageHelpText', + { + defaultMessage: + 'An optional background image to reveal over. ' + + 'Provide an image asset as a `{BASE64}` data {URL}, or pass in a sub-expression.', + values: { + BASE64, + URL, + }, + } + ), + origin: i18n.translate('expressionRevealImage.functions.revealImage.args.originHelpText', { defaultMessage: 'The position to start the image fill. For example, {list}, or {end}.', values: { list: Object.values(Position) @@ -50,7 +51,7 @@ export const help: FunctionHelp> = { export const errors = { invalidPercent: (percent: number) => new Error( - i18n.translate('xpack.canvas.functions.revealImage.invalidPercentErrorMessage', { + i18n.translate('expressionRevealImage.functions.revealImage.invalidPercentErrorMessage', { defaultMessage: "Invalid value: '{percent}'. Percentage must be between 0 and 1", values: { percent, diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts new file mode 100644 index 00000000000000..09cd26c9e620b9 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_errors.ts @@ -0,0 +1,13 @@ +/* + * 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 { errors as revealImage } from './dict/reveal_image'; + +export const getFunctionErrors = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts new file mode 100644 index 00000000000000..30e79b120771b7 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/function_help.ts @@ -0,0 +1,21 @@ +/* + * 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 { help as revealImage } from './dict/reveal_image'; + +/** + * Help text for Canvas Functions should be properly localized. This function will + * return a dictionary of help strings, organized by `ExpressionFunctionDefinition` + * specification and then by available arguments within each `ExpressionFunctionDefinition`. + * + * This a function, rather than an object, to future-proof string initialization, + * if ever necessary. + */ +export const getFunctionHelp = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts new file mode 100644 index 00000000000000..3d36b123421f48 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './function_help'; +export * from './function_errors'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts new file mode 100644 index 00000000000000..4f70f9d30b74bb --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts @@ -0,0 +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 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. + */ + +export { strings as revealImage } from './reveal_image'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts new file mode 100644 index 00000000000000..a32fdbd4c0b50f --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/reveal_image.ts @@ -0,0 +1,19 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const strings = { + getDisplayName: () => + i18n.translate('expressionRevealImage.renderer.revealImage.displayName', { + defaultMessage: 'Image reveal', + }), + getHelpDescription: () => + i18n.translate('expressionRevealImage.renderer.revealImage.helpDescription', { + defaultMessage: 'Reveal a percentage of an image to make a custom gauge-style chart', + }), +}; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts new file mode 100644 index 00000000000000..7e637f240d15c9 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/index.ts @@ -0,0 +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 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. + */ + +export * from './renderer_strings'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts new file mode 100644 index 00000000000000..b74230a2a5d76e --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/expression_renderers/renderer_strings.ts @@ -0,0 +1,21 @@ +/* + * 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 { revealImage } from './dict'; + +/** + * Help text for Canvas Functions should be properly localized. This function will + * return a dictionary of help strings, organized by `ExpressionFunctionDefinition` + * specification and then by available arguments within each `ExpressionFunctionDefinition`. + * + * This a function, rather than an object, to future-proof string initialization, + * if ever necessary. + */ +export const getRendererStrings = () => ({ + revealImage, +}); diff --git a/src/plugins/expression_reveal_image/common/i18n/index.ts b/src/plugins/expression_reveal_image/common/i18n/index.ts new file mode 100644 index 00000000000000..9c50bfab1305d3 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/i18n/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/common/index.ts b/src/plugins/expression_reveal_image/common/index.ts new file mode 100755 index 00000000000000..95503b36acdb62 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './constants'; +export * from './expression_functions'; diff --git a/src/plugins/expression_reveal_image/common/types/expression_functions.ts b/src/plugins/expression_reveal_image/common/types/expression_functions.ts new file mode 100644 index 00000000000000..ee291e204acfb8 --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/expression_functions.ts @@ -0,0 +1,42 @@ +/* + * 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 { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions'; + +export enum Origin { + TOP = 'top', + LEFT = 'left', + BOTTOM = 'bottom', + RIGHT = 'right', +} + +interface Arguments { + image: string | null; + emptyImage: string | null; + origin: Origin; +} + +export interface Output { + image: string; + emptyImage: string; + origin: Origin; + percent: number; +} + +export type ExpressionRevealImageFunction = () => ExpressionFunctionDefinition< + 'revealImage', + number, + Arguments, + ExpressionValueRender +>; + +export enum Position { + TOP = 'top', + BOTTOM = 'bottom', + LEFT = 'left', + RIGHT = 'right', +} diff --git a/src/plugins/expression_reveal_image/common/types/expression_renderers.ts b/src/plugins/expression_reveal_image/common/types/expression_renderers.ts new file mode 100644 index 00000000000000..77dacaefc1bd1f --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/expression_renderers.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export type OriginString = 'bottom' | 'left' | 'top' | 'right'; + +export interface RevealImageRendererConfig { + percent: number; + origin?: OriginString; + image?: string; + emptyImage?: string; +} + +export interface NodeDimensions { + width: number; + height: number; +} diff --git a/src/plugins/expression_reveal_image/common/types/index.ts b/src/plugins/expression_reveal_image/common/types/index.ts new file mode 100644 index 00000000000000..ec934e7affe88b --- /dev/null +++ b/src/plugins/expression_reveal_image/common/types/index.ts @@ -0,0 +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 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. + */ +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/jest.config.js b/src/plugins/expression_reveal_image/jest.config.js new file mode 100644 index 00000000000000..aac5fad293846b --- /dev/null +++ b/src/plugins/expression_reveal_image/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expression_reveal_image'], +}; diff --git a/src/plugins/expression_reveal_image/kibana.json b/src/plugins/expression_reveal_image/kibana.json new file mode 100755 index 00000000000000..9af9a5857dcfb9 --- /dev/null +++ b/src/plugins/expression_reveal_image/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "expressionRevealImage", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "presentationUtil"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/src/plugins/expression_reveal_image/public/components/index.ts b/src/plugins/expression_reveal_image/public/components/index.ts new file mode 100644 index 00000000000000..23cb4d7a20cb8e --- /dev/null +++ b/src/plugins/expression_reveal_image/public/components/index.ts @@ -0,0 +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 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. + */ + +export * from './reveal_image_component'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/reveal_image.scss b/src/plugins/expression_reveal_image/public/components/reveal_image.scss similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/reveal_image.scss rename to src/plugins/expression_reveal_image/public/components/reveal_image.scss diff --git a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx new file mode 100644 index 00000000000000..a9c24fca78d9b6 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx @@ -0,0 +1,136 @@ +/* + * 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, { useRef, useState, useEffect, useCallback } from 'react'; +import { useResizeObserver } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { NodeDimensions, RevealImageRendererConfig, OriginString } from '../../common/types'; +import { isValidUrl, elasticOutline } from '../../../presentation_util/public'; +import './reveal_image.scss'; + +interface RevealImageComponentProps extends RevealImageRendererConfig { + onLoaded: IInterpreterRenderHandlers['done']; + parentNode: HTMLElement; +} + +interface ImageStyles { + width?: string; + height?: string; + clipPath?: string; +} + +interface AlignerStyles { + backgroundImage?: string; +} + +function RevealImageComponent({ + onLoaded, + parentNode, + percent, + origin, + image, + emptyImage, +}: RevealImageComponentProps) { + const [loaded, setLoaded] = useState(false); + const [dimensions, setDimensions] = useState({ + width: 1, + height: 1, + }); + + const imgRef = useRef(null); + + const parentNodeDimensions = useResizeObserver(parentNode); + + // modify the top-level container class + parentNode.className = 'revealImage'; + + // set up the overlay image + const updateImageView = useCallback(() => { + if (imgRef.current) { + setDimensions({ + height: imgRef.current.naturalHeight, + width: imgRef.current.naturalWidth, + }); + + setLoaded(true); + onLoaded(); + } + }, [imgRef, onLoaded]); + + useEffect(() => { + updateImageView(); + }, [parentNodeDimensions, updateImageView]); + + function getClipPath(percentParam: number, originParam: OriginString = 'bottom') { + const directions: Record = { bottom: 0, left: 1, top: 2, right: 3 }; + const values: Array = [0, 0, 0, 0]; + values[directions[originParam]] = `${100 - percentParam * 100}%`; + return `inset(${values.join(' ')})`; + } + + function getImageSizeStyle() { + const imgStyles: ImageStyles = {}; + + const imgDimensions = { + height: dimensions.height, + width: dimensions.width, + ratio: dimensions.height / dimensions.width, + }; + + const domNodeDimensions = { + width: parentNode.clientWidth, + height: parentNode.clientHeight, + ratio: parentNode.clientHeight / parentNode.clientWidth, + }; + + if (imgDimensions.ratio > domNodeDimensions.ratio) { + imgStyles.height = `${domNodeDimensions.height}px`; + imgStyles.width = 'initial'; + } else { + imgStyles.width = `${domNodeDimensions.width}px`; + imgStyles.height = 'initial'; + } + + return imgStyles; + } + + const imgSrc = isValidUrl(image ?? '') ? image : elasticOutline; + + const alignerStyles: AlignerStyles = {}; + + if (isValidUrl(emptyImage ?? '')) { + // only use empty image if one is provided + alignerStyles.backgroundImage = `url(${emptyImage})`; + } + + let imgStyles: ImageStyles = {}; + if (imgRef.current && loaded) imgStyles = getImageSizeStyle(); + + imgStyles.clipPath = getClipPath(percent, origin); + if (imgRef.current && loaded) { + imgRef.current.style.setProperty('-webkit-clip-path', getClipPath(percent, origin)); + } + + return ( +
+ +
+ ); +} + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { RevealImageComponent as default }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/__snapshots__/reveal_image.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/reveal_image/__stories__/__snapshots__/reveal_image.stories.storyshot rename to src/plugins/expression_reveal_image/public/expression_renderers/__stories__/__snapshots__/reveal_image.stories.storyshot 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 new file mode 100644 index 00000000000000..bc70b3685e24e1 --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { storiesOf } from '@storybook/react'; +import { revealImageRenderer } from '../'; +import { elasticOutline, elasticLogo } from '../../../../presentation_util/public'; +import { Render } from '../../../../presentation_util/public/__stories__'; + +import { Origin } from '../../../common/types/expression_functions'; + +storiesOf('renderers/revealImage', module).add('default', () => { + const config = { + image: elasticLogo, + emptyImage: elasticOutline, + origin: Origin.LEFT, + percent: 0.45, + }; + + return ; +}); diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/index.ts b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts new file mode 100644 index 00000000000000..433a81884f157e --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { revealImageRenderer } from './reveal_image_renderer'; + +export const renderers = [revealImageRenderer]; + +export { revealImageRenderer }; 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 new file mode 100644 index 00000000000000..4d84de3da994ca --- /dev/null +++ b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx @@ -0,0 +1,42 @@ +/* + * 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, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { withSuspense } from '../../../presentation_util/public'; +import { getRendererStrings } from '../../common/i18n'; +import { RevealImageRendererConfig } from '../../common/types'; + +const { revealImage: revealImageStrings } = getRendererStrings(); + +const LazyRevealImageComponent = lazy(() => import('../components/reveal_image_component')); +const RevealImageComponent = withSuspense(LazyRevealImageComponent, null); + +export const revealImageRenderer = (): ExpressionRenderDefinition => ({ + name: 'revealImage', + displayName: revealImageStrings.getDisplayName(), + help: revealImageStrings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: RevealImageRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/expression_reveal_image/public/index.ts b/src/plugins/expression_reveal_image/public/index.ts new file mode 100755 index 00000000000000..00cb14e0fc064c --- /dev/null +++ b/src/plugins/expression_reveal_image/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { ExpressionRevealImagePlugin } from './plugin'; + +export type { ExpressionRevealImagePluginSetup, ExpressionRevealImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionRevealImagePlugin(); +} + +export * from './expression_renderers'; diff --git a/src/plugins/expression_reveal_image/public/plugin.ts b/src/plugins/expression_reveal_image/public/plugin.ts new file mode 100755 index 00000000000000..5f6496a25f820a --- /dev/null +++ b/src/plugins/expression_reveal_image/public/plugin.ts @@ -0,0 +1,39 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; +import { revealImageRenderer } from './expression_renderers'; + +interface SetupDeps { + expressions: ExpressionsSetup; +} + +interface StartDeps { + expression: ExpressionsStart; +} + +export type ExpressionRevealImagePluginSetup = void; +export type ExpressionRevealImagePluginStart = void; + +export class ExpressionRevealImagePlugin + implements + Plugin< + ExpressionRevealImagePluginSetup, + ExpressionRevealImagePluginStart, + SetupDeps, + StartDeps + > { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRevealImagePluginSetup { + expressions.registerRenderer(revealImageRenderer); + } + + public start(core: CoreStart): ExpressionRevealImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_reveal_image/server/index.ts b/src/plugins/expression_reveal_image/server/index.ts new file mode 100644 index 00000000000000..b86c356974321d --- /dev/null +++ b/src/plugins/expression_reveal_image/server/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { ExpressionRevealImagePlugin } from './plugin'; + +export type { ExpressionRevealImagePluginSetup, ExpressionRevealImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionRevealImagePlugin(); +} diff --git a/src/plugins/expression_reveal_image/server/plugin.ts b/src/plugins/expression_reveal_image/server/plugin.ts new file mode 100644 index 00000000000000..446ef018eb7d3e --- /dev/null +++ b/src/plugins/expression_reveal_image/server/plugin.ts @@ -0,0 +1,39 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server'; +import { revealImageFunction } from '../common'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionRevealImagePluginSetup = void; +export type ExpressionRevealImagePluginStart = void; + +export class ExpressionRevealImagePlugin + implements + Plugin< + ExpressionRevealImagePluginSetup, + ExpressionRevealImagePluginStart, + SetupDeps, + StartDeps + > { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRevealImagePluginSetup { + expressions.registerFunction(revealImageFunction); + } + + public start(core: CoreStart): ExpressionRevealImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_reveal_image/tsconfig.json b/src/plugins/expression_reveal_image/tsconfig.json new file mode 100644 index 00000000000000..aa4562ec735765 --- /dev/null +++ b/src/plugins/expression_reveal_image/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" }, + ] +} diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index 7ea96ee7fdde82..d897e24aaad83c 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -10,7 +10,7 @@ import { Observable, defer, of, zip } from 'rxjs'; import { map } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; -import { Datatable, DatatableColumn, getType } from '../../expression_types'; +import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../expression_types'; export interface MapColumnArguments { id?: string | null; @@ -103,7 +103,16 @@ export const mapColumn: ExpressionFunctionDefinition< return rows$.pipe( map((rows) => { - const type = getType(rows[0]?.[id]); + let type: DatatableColumnType = 'null'; + if (rows.length) { + for (const row of rows) { + const rowType = getType(row[id]); + if (rowType !== 'null') { + type = rowType; + break; + } + } + } const newColumn: DatatableColumn = { id, name: args.name, diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts index 633d912c29502f..c59016cd260aba 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { math, MathArguments } from './math'; -import { Datatable, DatatableColumn, getType } from '../../expression_types'; +import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../expression_types'; export type MathColumnArguments = MathArguments & { id: string; @@ -104,7 +104,16 @@ export const mathColumn: ExpressionFunctionDefinition< return { ...row, [args.id]: result }; }); - const type = newRows.length ? getType(newRows[0][args.id]) : 'null'; + let type: DatatableColumnType = 'null'; + if (newRows.length) { + for (const row of newRows) { + const rowType = getType(row[args.id]); + if (rowType !== 'null') { + type = rowType; + break; + } + } + } const newColumn: DatatableColumn = { id: args.id, name: args.name ?? args.id, diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index bd934745fed723..64a42958ae8a26 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -10,9 +10,10 @@ import { of, Observable } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { Datatable } from '../../../expression_types'; import { mapColumn, MapColumnArguments } from '../map_column'; -import { emptyTable, functionWrapper, testTable } from './utils'; +import { emptyTable, functionWrapper, testTable, tableWithNulls } from './utils'; -const pricePlusTwo = (datatable: Datatable) => of(datatable.rows[0].price + 2); +const pricePlusTwo = (datatable: Datatable) => + of(typeof datatable.rows[0].price === 'number' ? datatable.rows[0].price + 2 : null); describe('mapColumn', () => { const fn = functionWrapper(mapColumn); @@ -219,11 +220,11 @@ describe('mapColumn', () => { }); }); - it('should correctly infer the type fromt he first row if the references column for meta information does not exists', () => { + it('should correctly infer the type from the first row if the references column for meta information does not exists', () => { testScheduler.run(({ expectObservable }) => { expectObservable( runFn( - { ...emptyTable, rows: [...emptyTable.rows, { value: 5 }] }, + { ...emptyTable, rows: [...emptyTable.rows, { price: 5 }] }, { name: 'value', copyMetaFrom: 'time', expression: pricePlusTwo } ) ).toBe('(0|)', [ @@ -236,6 +237,31 @@ describe('mapColumn', () => { meta: expect.objectContaining({ type: 'number' }), }), ], + rows: [{ price: 5, value: 7 }], + }), + ]); + }); + }); + + it('should correctly infer the type from the first non-null row', () => { + testScheduler.run(({ expectObservable }) => { + expectObservable( + runFn(tableWithNulls, { + id: 'value', + name: 'value', + expression: pricePlusTwo, + }) + ).toBe('(0|)', [ + expect.objectContaining({ + type: 'datatable', + columns: [ + ...tableWithNulls.columns, + expect.objectContaining({ + id: 'value', + name: 'value', + meta: expect.objectContaining({ type: 'number' }), + }), + ], }), ]); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts index e0fb0a3a9f23d5..3464736fe0ad35 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts @@ -7,7 +7,7 @@ */ import { mathColumn } from '../math_column'; -import { functionWrapper, testTable } from './utils'; +import { functionWrapper, testTable, tableWithNulls } from './utils'; describe('mathColumn', () => { const fn = functionWrapper(mathColumn); @@ -95,4 +95,22 @@ describe('mathColumn', () => { meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, }); }); + + it('should correctly infer the type from the first non-null row', () => { + expect( + fn(tableWithNulls, { id: 'value', name: 'value', expression: 'price + 2', onError: 'null' }) + ).toEqual( + expect.objectContaining({ + type: 'datatable', + columns: [ + ...tableWithNulls.columns, + expect.objectContaining({ + id: 'value', + name: 'value', + meta: expect.objectContaining({ type: 'number' }), + }), + ], + }) + ); + }); }); diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts index 60d22d2b8575cb..ca41b427a28f7a 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts @@ -224,4 +224,72 @@ const stringTable: Datatable = { ], }; -export { emptyTable, testTable, stringTable }; +const tableWithNulls: Datatable = { + type: 'datatable', + columns: [ + { + id: 'name', + name: 'name label', + meta: { type: 'string' }, + }, + { + id: 'time', + name: 'time label', + meta: { type: 'date' }, + }, + { + id: 'price', + name: 'price label', + meta: { type: 'number' }, + }, + ], + rows: [ + { + name: 'product1', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: null, + }, + { + name: 'product1', + time: 1517929200950, // 06 Feb 2018 15:00:00 GMT + price: null, + }, + { + name: 'product1', + time: 1518015600950, // 07 Feb 2018 15:00:00 GMT + price: 420, + }, + { + name: 'product2', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 216, + }, + { + name: 'product2', + time: 1517929200950, // 06 Feb 2018 15:00:00 GMT + price: 200, + }, + { + name: 'product2', + time: 1518015600950, // 07 Feb 2018 15:00:00 GMT + price: 190, + }, + { + name: 'product3', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: null, + }, + { + name: 'product4', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 311, + }, + { + name: 'product5', + time: 1517842800950, // 05 Feb 2018 15:00:00 GMT + price: 288, + }, + ], +}; + +export { emptyTable, testTable, stringTable, tableWithNulls }; diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap index 957c94c80680d9..75b8177d9dac35 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap @@ -120,11 +120,10 @@ exports[`EmptyState should render normally 1`] = `
- + - +
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx index c05f6a1f193b7a..af49e8c36fe3bd 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx @@ -24,6 +24,7 @@ import { EuiCard, EuiLink, EuiText, + EuiFlexGroup, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { reactRouterNavigate } from '../../../../../../plugins/kibana_react/public'; @@ -143,8 +144,8 @@ export const EmptyState = ({
- - + + - + - +
diff --git a/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap b/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap index 71d034d32bd1be..9ad2bd73674bd8 100644 --- a/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap +++ b/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap @@ -2,7 +2,6 @@ exports[`KibanaPageTemplate render basic template 1`] = ` = ({ ); } + const emptyStateDefaultTemplate = pageSideBar ? 'centeredContent' : 'centeredBody'; + /** * An easy way to create the right content for empty pages */ if (isEmptyState && pageHeader && !children) { - template = template ?? 'centeredBody'; + template = template ?? emptyStateDefaultTemplate; const { iconType, pageTitle, description, rightSideItems } = pageHeader; pageHeader = undefined; children = ( @@ -104,14 +106,13 @@ export const KibanaPageTemplate: FunctionComponent = ({ } else if (isEmptyState && pageHeader && children) { template = template ?? 'centeredContent'; } else if (isEmptyState && !pageHeader) { - template = template ?? 'centeredBody'; + template = template ?? emptyStateDefaultTemplate; } return ( void; setBreadcrumbs: () => void; } -export const ManagementLandingPage = ({ version, setBreadcrumbs }: ManagementLandingPageProps) => { +export const ManagementLandingPage = ({ + version, + setBreadcrumbs, + onAppMounted, +}: ManagementLandingPageProps) => { setBreadcrumbs(); + useEffect(() => { + onAppMounted(''); + }, [onAppMounted]); + return ( )} /> diff --git a/src/plugins/presentation_util/common/labs.ts b/src/plugins/presentation_util/common/labs.ts index d80624fe0bb99f..b958f3de0814f9 100644 --- a/src/plugins/presentation_util/common/labs.ts +++ b/src/plugins/presentation_util/common/labs.ts @@ -9,10 +9,9 @@ import { i18n } from '@kbn/i18n'; export const LABS_PROJECT_PREFIX = 'labs:'; -export const TIME_TO_PRESENT = `${LABS_PROJECT_PREFIX}presentation:timeToPresent` as const; export const DEFER_BELOW_FOLD = `${LABS_PROJECT_PREFIX}dashboard:deferBelowFold` as const; -export const projectIDs = [TIME_TO_PRESENT, DEFER_BELOW_FOLD] as const; +export const projectIDs = [DEFER_BELOW_FOLD] as const; export const environmentNames = ['kibana', 'browser', 'session'] as const; export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const; @@ -21,30 +20,17 @@ export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const; * provided to users of our solutions in Kibana. */ export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = { - [TIME_TO_PRESENT]: { - id: TIME_TO_PRESENT, - isActive: false, - isDisplayed: false, - environments: ['kibana', 'browser', 'session'], - name: i18n.translate('presentationUtil.labs.enableTimeToPresentProjectName', { - defaultMessage: 'Canvas Presentation UI', - }), - description: i18n.translate('presentationUtil.labs.enableUnifiedToolbarProjectDescription', { - defaultMessage: 'Enable the new presentation-oriented UI for Canvas.', - }), - solutions: ['canvas'], - }, [DEFER_BELOW_FOLD]: { id: DEFER_BELOW_FOLD, isActive: false, isDisplayed: true, environments: ['kibana', 'browser', 'session'], name: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectName', { - defaultMessage: 'Defer loading below "the fold"', + defaultMessage: 'Defer loading panels below "the fold"', }), description: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectDescription', { defaultMessage: - 'Any Dashboard panels below the fold-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport', + 'Any panels below "the fold"-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport', }), solutions: ['dashboard'], }, diff --git a/src/plugins/presentation_util/common/lib/index.ts b/src/plugins/presentation_util/common/lib/index.ts new file mode 100644 index 00000000000000..3fe90009ad8dff --- /dev/null +++ b/src/plugins/presentation_util/common/lib/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export * from './utils'; +export * from './test_helpers'; diff --git a/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts b/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts new file mode 100644 index 00000000000000..4ec02fd622cf74 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/test_helpers/function_wrapper.ts @@ -0,0 +1,27 @@ +/* + * 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 { mapValues } from 'lodash'; +import { + ExpressionValueBoxed, + typeSpecs, + ExpressionFunctionDefinition, +} from '../../../../expressions/common'; + +type FnType = () => typeof typeSpecs[number] & + ExpressionFunctionDefinition, ExpressionValueBoxed>; + +// It takes a function spec and passes in default args into the spec fn +export const functionWrapper = (fnSpec: FnType): ReturnType['fn'] => { + const spec = fnSpec(); + const defaultArgs = mapValues(spec.args, (argSpec) => { + return argSpec.default; + }); + + return (context, args, handlers) => spec.fn(context, { ...defaultArgs, ...args }, handlers); +}; diff --git a/src/plugins/presentation_util/common/lib/test_helpers/index.ts b/src/plugins/presentation_util/common/lib/test_helpers/index.ts new file mode 100644 index 00000000000000..a6ea8da6ac6e91 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/test_helpers/index.ts @@ -0,0 +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 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. + */ + +export * from './function_wrapper'; diff --git a/x-pack/plugins/canvas/common/lib/dataurl.test.ts b/src/plugins/presentation_util/common/lib/utils/dataurl.test.ts similarity index 94% rename from x-pack/plugins/canvas/common/lib/dataurl.test.ts rename to src/plugins/presentation_util/common/lib/utils/dataurl.test.ts index 9ddd0a50ea9d5c..5820b10f589fe0 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/dataurl.test.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 { isValidDataUrl, parseDataUrl } from './dataurl'; diff --git a/x-pack/plugins/canvas/common/lib/dataurl.ts b/src/plugins/presentation_util/common/lib/utils/dataurl.ts similarity index 90% rename from x-pack/plugins/canvas/common/lib/dataurl.ts rename to src/plugins/presentation_util/common/lib/utils/dataurl.ts index 2ae28b621c425f..9ac232369cdc1f 100644 --- a/x-pack/plugins/canvas/common/lib/dataurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/dataurl.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 { fromByteArray } from 'base64-js'; diff --git a/x-pack/plugins/canvas/public/lib/elastic_logo.ts b/src/plugins/presentation_util/common/lib/utils/elastic_logo.ts similarity index 96% rename from x-pack/plugins/canvas/public/lib/elastic_logo.ts rename to src/plugins/presentation_util/common/lib/utils/elastic_logo.ts index 81c79c39143d6e..9a789d1a5fb03e 100644 --- a/x-pack/plugins/canvas/public/lib/elastic_logo.ts +++ b/src/plugins/presentation_util/common/lib/utils/elastic_logo.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. */ export const elasticLogo = diff --git a/src/plugins/presentation_util/common/lib/utils/elastic_outline.ts b/src/plugins/presentation_util/common/lib/utils/elastic_outline.ts new file mode 100644 index 00000000000000..4747be58127f75 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/elastic_outline.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const elasticOutline = + 'data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E'; diff --git a/x-pack/plugins/canvas/common/lib/httpurl.test.ts b/src/plugins/presentation_util/common/lib/utils/httpurl.test.ts similarity index 89% rename from x-pack/plugins/canvas/common/lib/httpurl.test.ts rename to src/plugins/presentation_util/common/lib/utils/httpurl.test.ts index 1cd00114bf7ca0..20cd40480691d8 100644 --- a/x-pack/plugins/canvas/common/lib/httpurl.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/httpurl.test.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 { isValidHttpUrl } from './httpurl'; diff --git a/x-pack/plugins/canvas/common/lib/httpurl.ts b/src/plugins/presentation_util/common/lib/utils/httpurl.ts similarity index 67% rename from x-pack/plugins/canvas/common/lib/httpurl.ts rename to src/plugins/presentation_util/common/lib/utils/httpurl.ts index 4f8b03aa2a0622..4777eb4c8128d5 100644 --- a/x-pack/plugins/canvas/common/lib/httpurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/httpurl.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. */ // A cheap regex to distinguish an HTTP URL string from a data URL string diff --git a/src/plugins/presentation_util/common/lib/utils/index.ts b/src/plugins/presentation_util/common/lib/utils/index.ts new file mode 100644 index 00000000000000..eed4acf78b2bef --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/index.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +export * from './dataurl'; +export * from './elastic_logo'; +export * from './elastic_outline'; +export * from './httpurl'; +export * from './missing_asset'; +export * from './resolve_dataurl'; +export * from './url'; diff --git a/src/plugins/presentation_util/common/lib/utils/missing_asset.ts b/src/plugins/presentation_util/common/lib/utils/missing_asset.ts new file mode 100644 index 00000000000000..10d429870c88c1 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/missing_asset.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +// CC0, source: https://pixabay.com/en/question-mark-confirmation-question-838656/ +export const missingImage = + 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMSAzMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48ZmlsdGVyIGlkPSJiIiB4PSItLjM2IiB5PSItLjM2IiB3aWR0aD0iMS43MiIgaGVpZ2h0PSIxLjcyIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIuNDY0Ii8+PC9maWx0ZXI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMTU5LjM0IiB4Mj0iMTg0LjQ4IiB5MT0iNzI3LjM2IiB5Mj0iNzQ5Ljg5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMTEuNTMgLTU0OS42OCkgc2NhbGUoLjc3MDAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNlYmYwZWQiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNmYWZhZmEiIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xNS40MzcgMi42OTVsMTQuNTA2IDI0LjQ3NkgxLjI4N2wxNC4xNS0yNC40NzZ6IiBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9InJlZCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggdHJhbnNmb3JtPSJtYXRyaXgoLjgzMTk3IDAgMCAuNTU0NjYgLTc4LjU4MyAtMzgzLjUxKSIgZD0iTTExMS4zMSA3MzEuMmMtMy4yODMtMy45MjUtMy41OTUtNi4xNDgtMi4wMjQtMTAuNDM4IDMuMzM2LTYuMTQ1IDQuNDk2LTguMDY4IDUuNDEtOS40MDUgMS45MDEgNS4xNjIgMi4xMjYgMTkuMTQtMy4zODYgMTkuODQzeiIgZmlsbD0iI2ZmZiIgZmlsbC1vcGFjaXR5PSIuODc2IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbHRlcj0idXJsKCNiKSIvPjxnIGZpbGwtb3BhY2l0eT0iLjgyIj48cGF0aCBkPSJNMTUuMDQ2IDIwLjIyN2gtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY3IDAtLjg5Ni0uMTc1LTEuNTU3LS41MjYtMS45ODItLjM1LS40MjUtLjc2NS0uNjM3LTEuMjQ0LS42MzctLjM2NCAwLS42Ny4wOTgtLjkyLjI5My0uMTg5LjE0OS0uMjgzLjMwNC0uMjgzLjQ2NiAwIC4xMDcuMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDk0IDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNTEuNTQtMS4wMjkgMS4xNC0yLjAzMyAxLjgtLjY3NC40NDUtMS4xMi44NDMtMS4zMzUgMS4xOTQtLjIxLjM0My0uMzM3Ljg3My0uMzg0IDEuNTg3bS0uMTEyIDEuNDc3Yy40NTIgMCAuODM2LjE1OCAxLjE1My40NzUuMzE3LjMxNy40NzYuNzAxLjQ3NiAxLjE1MyAwIC40NTItLjE1OS44NC0uNDc2IDEuMTYzYTEuNTcgMS41NyAwIDAgMS0xLjE1My40NzUgMS41NyAxLjU3IDAgMCAxLTEuMTUzLS40NzUgMS42MDQgMS42MDQgMCAwIDEtLjQ3NS0xLjE2M2MwLS40NTIuMTU5LS44MzYuNDc1LTEuMTUzYTEuNTcgMS41NyAwIDAgMSAxLjE1My0uNDc1IiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9Ii40ODYiLz48cGF0aCBkPSJNMTUuMzI3IDIwLjUwOGgtLjQxNWMtLjAxMy0uNzQ4LjAyLTEuMzA4LjEwMS0xLjY3OC4wODgtLjM3MS4zMDctLjg4LjY1OC0xLjUyOC4zNTctLjY1NC41OS0xLjE3Ni42OTctMS41NjcuMTE1LS4zOTguMTcyLS44ODcuMTcyLTEuNDY2IDAtLjg5Ny0uMTc1LTEuNTU4LS41MjYtMS45ODMtLjM1LS40MjQtLjc2NS0uNjM3LTEuMjQzLS42MzctLjM2NSAwLS42NzEuMDk4LS45Mi4yOTMtLjE5LjE0OS0uMjg0LjMwNC0uMjg0LjQ2NiAwIC4xMDguMDY0LjI3Ni4xOTIuNTA1LjI5LjUyLjQzNS45NjEuNDM1IDEuMzI1IDAgLjMzLS4xMTUuNjA3LS4zNDQuODNhMS4xMzggMS4xMzggMCAwIDEtLjg0LjMzM2MtLjM3NyAwLS42OTQtLjEzMS0uOTUtLjM5NC0uMjU2LS4yNy0uMzg0LS42MjQtLjM4NC0xLjA2MiAwLS43OTYuMzQ0LTEuNDkzIDEuMDMxLTIuMDk0LjY4OC0uNiAxLjY0OS0uOSAyLjg4My0uOSAxLjMwOCAwIDIuMzAyLjMxNCAyLjk4My45NC42ODguNjIxIDEuMDMyIDEuMzczIDEuMDMyIDIuMjU2IDAgLjY0LS4xNzYgMS4yMzQtLjUyNiAxLjc4LS4zNS41NC0xLjAyOCAxLjE0LTIuMDMzIDEuOC0uNjc0LjQ0NS0uODUzLjg0My0xLjA2OCAxLjE5NC0uMjEuMzQzLS4zMzcuODczLS4zODUgMS41ODdtLS4zNzggMS40NzdjLjQ1MiAwIC44MzYuMTU4IDEuMTUzLjQ3NS4zMTcuMzE3LjQ3Ni43MDEuNDc2IDEuMTUzIDAgLjQ1Mi0uMTU5Ljg0LS40NzYgMS4xNjNhMS41NyAxLjU3IDAgMCAxLTEuMTUzLjQ3NiAxLjU3IDEuNTcgMCAwIDEtMS4xNTMtLjQ3NiAxLjYwNCAxLjYwNCAwIDAgMS0uNDc1LTEuMTYzYzAtLjQ1Mi4xNTktLjgzNi40NzUtMS4xNTNhMS41NyAxLjU3IDAgMCAxIDEuMTUzLS40NzUiIGZpbGwtb3BhY2l0eT0iLjgyIi8+PC9nPjwvc3ZnPg=='; diff --git a/x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts similarity index 84% rename from x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js rename to src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts index 72aaa1dfbd5029..c2b9a444d20ef2 100644 --- a/x-pack/plugins/canvas/common/lib/resolve_dataurl.test.js +++ b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.test.ts @@ -1,11 +1,12 @@ /* * 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 { missingImage } from '../../common/lib/missing_asset'; +import { missingImage } from './missing_asset'; import { resolveFromArgs, resolveWithMissingImage } from './resolve_dataurl'; describe('resolve_dataurl', () => { diff --git a/x-pack/plugins/canvas/common/lib/resolve_dataurl.ts b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts similarity index 75% rename from x-pack/plugins/canvas/common/lib/resolve_dataurl.ts rename to src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts index 79e49c0595355f..db94bdf04c32bf 100644 --- a/x-pack/plugins/canvas/common/lib/resolve_dataurl.ts +++ b/src/plugins/presentation_util/common/lib/utils/resolve_dataurl.ts @@ -1,13 +1,14 @@ /* * 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 { get } from 'lodash'; -import { isValidUrl } from '../../common/lib/url'; -import { missingImage } from '../../common/lib/missing_asset'; +import { isValidUrl } from './url'; +import { missingImage } from './missing_asset'; /* * NOTE: args.dataurl can come as an expression here. diff --git a/x-pack/plugins/canvas/common/lib/url.test.ts b/src/plugins/presentation_util/common/lib/utils/url.test.ts similarity index 70% rename from x-pack/plugins/canvas/common/lib/url.test.ts rename to src/plugins/presentation_util/common/lib/utils/url.test.ts index 654602eea2093a..4599e776a62661 100644 --- a/x-pack/plugins/canvas/common/lib/url.test.ts +++ b/src/plugins/presentation_util/common/lib/utils/url.test.ts @@ -1,11 +1,12 @@ /* * 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 { missingImage } from '../../common/lib/missing_asset'; +import { missingImage } from './missing_asset'; import { isValidUrl } from './url'; describe('resolve_dataurl', () => { diff --git a/src/plugins/presentation_util/common/lib/utils/url.ts b/src/plugins/presentation_util/common/lib/utils/url.ts new file mode 100644 index 00000000000000..e6a1064200cc15 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/utils/url.ts @@ -0,0 +1,14 @@ +/* + * 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 { isValidDataUrl } from './dataurl'; +import { isValidHttpUrl } from './httpurl'; + +export function isValidUrl(url: string) { + return isValidDataUrl(url) || isValidHttpUrl(url); +} diff --git a/src/plugins/presentation_util/jest.config.js b/src/plugins/presentation_util/jest.config.js new file mode 100644 index 00000000000000..2250d70acb4755 --- /dev/null +++ b/src/plugins/presentation_util/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/presentation_util'], +}; diff --git a/src/plugins/presentation_util/kibana.json b/src/plugins/presentation_util/kibana.json index c7d272dcd02a1c..22ec919457cce4 100644 --- a/src/plugins/presentation_util/kibana.json +++ b/src/plugins/presentation_util/kibana.json @@ -4,6 +4,9 @@ "kibanaVersion": "kibana", "server": true, "ui": true, + "extraPublicDirs": [ + "common/lib" + ], "requiredPlugins": [ "savedObjects" ], diff --git a/src/plugins/presentation_util/public/__stories__/index.tsx b/src/plugins/presentation_util/public/__stories__/index.tsx new file mode 100644 index 00000000000000..078a16cb8cab24 --- /dev/null +++ b/src/plugins/presentation_util/public/__stories__/index.tsx @@ -0,0 +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 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. + */ + +export * from './render'; diff --git a/src/plugins/presentation_util/public/__stories__/render.tsx b/src/plugins/presentation_util/public/__stories__/render.tsx new file mode 100644 index 00000000000000..29d95e6bf28191 --- /dev/null +++ b/src/plugins/presentation_util/public/__stories__/render.tsx @@ -0,0 +1,61 @@ +/* + * 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 { action } from '@storybook/addon-actions'; +import React, { useRef, useEffect } from 'react'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; + +export const defaultHandlers: IInterpreterRenderHandlers = { + getRenderMode: () => 'display', + isSyncColorsEnabled: () => false, + done: action('done'), + onDestroy: action('onDestroy'), + reload: action('reload'), + update: action('update'), + event: action('event'), +}; + +/* + Uses a RenderDefinitionFactory and Config to render into an element. + + Intended to be used for stories for RenderDefinitionFactory +*/ +interface RenderAdditionalProps { + height?: string; + width?: string; + handlers?: IInterpreterRenderHandlers; +} + +export const Render = ({ + renderer, + config, + ...rest +}: Renderer extends () => ExpressionRenderDefinition + ? { renderer: Renderer; config: Config } & RenderAdditionalProps + : { renderer: undefined; config: undefined } & RenderAdditionalProps) => { + const { height, width, handlers } = { + height: '200px', + width: '200px', + handlers: defaultHandlers, + ...rest, + }; + + const containerRef = useRef(null); + + useEffect(() => { + if (renderer && containerRef.current !== null) { + renderer().render(containerRef.current, config, handlers); + } + }, [renderer, config, handlers]); + + return ( +
+ {' '} +
+ ); +}; diff --git a/src/plugins/presentation_util/public/components/labs/project_list.tsx b/src/plugins/presentation_util/public/components/labs/project_list.tsx index 301fd1aa6414f5..ee1997b5ca7d8e 100644 --- a/src/plugins/presentation_util/public/components/labs/project_list.tsx +++ b/src/plugins/presentation_util/public/components/labs/project_list.tsx @@ -22,7 +22,20 @@ export interface Props { onStatusChange: ProjectListItemProps['onStatusChange']; } -const EmptyList = () => ; +const EmptyList = ({ solutions }: { solutions?: SolutionName[] }) => { + let title = strings.getNoProjectsMessage(); + + if (solutions?.length === 1) { + const solution = solutions[0]; + switch (solution) { + case 'dashboard': + title = strings.getNoProjectsInSolutionMessage('Dashboard'); + case 'canvas': + title = strings.getNoProjectsInSolutionMessage('Canvas'); + } + } + return ; +}; export const ProjectList = (props: Props) => { const { solutions, projects, onStatusChange } = props; @@ -48,7 +61,7 @@ export const ProjectList = (props: Props) => { return ( - {items.length > 0 ?
    {items}
: } + {items.length > 0 ?
    {items}
: }
); }; diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss index 4fc3651ee9f730..a1e5b4e1417652 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss @@ -6,7 +6,9 @@ border-color: $euiBorderColor !important; // sass-lint:disable-line no-important @include kbnThemeStyle('v8') { - border-width: $euiBorderWidthThin; - border-style: solid; + &[class*='--text'] { + border-width: $euiBorderWidthThin; + border-style: solid; + } } } diff --git a/src/plugins/presentation_util/public/i18n/labs.tsx b/src/plugins/presentation_util/public/i18n/labs.tsx index d9e34fa43ebb78..487c6fa6641e4d 100644 --- a/src/plugins/presentation_util/public/i18n/labs.tsx +++ b/src/plugins/presentation_util/public/i18n/labs.tsx @@ -19,8 +19,7 @@ export const LabsStrings = { defaultMessage: 'Kibana', }), help: i18n.translate('presentationUtil.labs.components.kibanaSwitchHelp', { - defaultMessage: - 'Sets the corresponding Advanced Setting for this lab project; affects all Kibana users', + defaultMessage: 'Enables this lab for all Kibana users.', }), }), getBrowserSwitchText: () => ({ @@ -28,8 +27,7 @@ export const LabsStrings = { defaultMessage: 'Browser', }), help: i18n.translate('presentationUtil.labs.components.browserSwitchHelp', { - defaultMessage: - 'Enables or disables the lab project for the browser; persists between browser instances', + defaultMessage: 'Enables the lab for this browser and persists after it closes.', }), }), getSessionSwitchText: () => ({ @@ -37,21 +35,27 @@ export const LabsStrings = { defaultMessage: 'Session', }), help: i18n.translate('presentationUtil.labs.components.sessionSwitchHelp', { - defaultMessage: - 'Enables or disables the lab project for this tab; resets when the browser tab is closed', + defaultMessage: 'Enables the lab for this browser session, so it resets when it closes.', }), }), }, List: { getNoProjectsMessage: () => i18n.translate('presentationUtil.labs.components.noProjectsMessage', { - defaultMessage: 'No available lab projects', + defaultMessage: 'No labs currently available.', + }), + getNoProjectsInSolutionMessage: (solutionName: string) => + i18n.translate('presentationUtil.labs.components.noProjectsinSolutionMessage', { + defaultMessage: 'No labs currently in {solutionName}.', + values: { + solutionName, + }, }), }, ListItem: { getOverrideLegend: () => i18n.translate('presentationUtil.labs.components.overrideFlagsLabel', { - defaultMessage: 'Override flags', + defaultMessage: 'Overrides', }), getOverriddenIconTipLabel: () => i18n.translate('presentationUtil.labs.components.overridenIconTipLabel', { @@ -81,12 +85,11 @@ export const LabsStrings = { Flyout: { getTitleLabel: () => i18n.translate('presentationUtil.labs.components.titleLabel', { - defaultMessage: 'Lab projects', + defaultMessage: 'Labs', }), getDescriptionMessage: () => i18n.translate('presentationUtil.labs.components.descriptionMessage', { - defaultMessage: - 'Lab projects are features and functionality that are in-progress or experimental in nature. They can be enabled and disabled locally for your browser or tab, or in Kibana.', + defaultMessage: 'Try out our features that are in progress or experimental.', }), getResetToDefaultLabel: () => i18n.translate('presentationUtil.labs.components.resetToDefaultLabel', { diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 5ad81c7e759bce..1e26011ff58ae6 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -15,9 +15,20 @@ export { getStubPluginServices, } from './services'; +export { + KibanaPluginServiceFactory, + PluginServiceFactory, + PluginServices, + PluginServiceProviders, + PluginServiceProvider, + PluginServiceRegistry, + KibanaPluginServiceParams, +} from './services/create'; + export { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; export { SaveModalDashboardProps } from './components/types'; export { projectIDs, ProjectID, Project } from '../common/labs'; +export * from '../common/lib'; export { LazyLabsBeakerButton, diff --git a/src/plugins/presentation_util/tsconfig.json b/src/plugins/presentation_util/tsconfig.json index c0fafe8c3aabad..b389d94b194136 100644 --- a/src/plugins/presentation_util/tsconfig.json +++ b/src/plugins/presentation_util/tsconfig.json @@ -7,6 +7,9 @@ "declaration": true, "declarationMap": true }, + "extraPublicDirs": [ + "common" + ], "include": [ "common/**/*", "public/**/*", diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index da65b5b9fdda8c..0a2e4ff78be26f 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; @@ -116,7 +116,7 @@ class SavedObjectFinderUi extends React.Component< private isComponentMounted: boolean = false; - private debouncedFetch = _.debounce(async (query: string) => { + private debouncedFetch = debounce(async (query: string) => { const metaDataMap = this.getSavedObjectMetaDataMap(); const fields = Object.values(metaDataMap) diff --git a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts index 1f2f7dc573dc74..40baff22f52c8f 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { cloneDeep, defaults, forOwn, assign } from 'lodash'; import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types'; import { SavedObjectNotFound } from '../../../../kibana_utils/public'; import { @@ -28,7 +28,7 @@ export async function applyESResp( ) { const mapping = expandShorthand(config.mapping ?? {}); const savedObjectType = config.type || ''; - savedObject._source = _.cloneDeep(resp._source); + savedObject._source = cloneDeep(resp._source); if (typeof resp.found === 'boolean' && !resp.found) { throw new SavedObjectNotFound(savedObjectType, savedObject.id || ''); } @@ -42,10 +42,10 @@ export async function applyESResp( } // assign the defaults to the response - _.defaults(savedObject._source, savedObject.defaults); + defaults(savedObject._source, savedObject.defaults); // transform the source using _deserializers - _.forOwn(mapping, (fieldMapping, fieldName) => { + forOwn(mapping, (fieldMapping, fieldName) => { if (fieldMapping._deserialize && typeof fieldName === 'string') { savedObject._source[fieldName] = fieldMapping._deserialize( savedObject._source[fieldName] as string @@ -54,7 +54,7 @@ export async function applyESResp( }); // Give obj all of the values in _source.fields - _.assign(savedObject, savedObject._source); + assign(savedObject, savedObject._source); savedObject.lastSavedTitle = savedObject.title; if (meta.searchSourceJSON) { diff --git a/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts b/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts index f1bc614dd11972..7ed729b4b7a0f6 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/create_source.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { SavedObjectAttributes } from 'kibana/public'; import { SavedObject, SavedObjectKibanaServices } from '../../types'; @@ -40,7 +40,7 @@ export async function createSource( return await savedObjectsClient.create(esType, source, options); } catch (err) { // record exists, confirm overwriting - if (_.get(err, 'res.status') === 409) { + if (get(err, 'res.status') === 409) { const confirmMessage = i18n.translate( 'savedObjects.confirmModal.overwriteConfirmationMessage', { diff --git a/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts index cf0cea8d368da9..b6dddf8d82b725 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/initialize_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { cloneDeep, assign } from 'lodash'; import { SavedObjectsClientContract } from 'kibana/public'; import { SavedObject, SavedObjectConfig } from '../../types'; @@ -24,7 +24,7 @@ export async function intializeSavedObject( if (!savedObject.id) { // just assign the defaults and be done - _.assign(savedObject, savedObject.defaults); + assign(savedObject, savedObject.defaults); await savedObject.hydrateIndexPattern!(); if (typeof config.afterESResp === 'function') { savedObject = await config.afterESResp(savedObject); @@ -36,7 +36,7 @@ export async function intializeSavedObject( const respMapped = { _id: resp.id, _type: resp.type, - _source: _.cloneDeep(resp.attributes), + _source: cloneDeep(resp.attributes), references: resp.references, found: !!resp._version, }; diff --git a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts index eb9bef788fcdc6..efe7a85f8f1e13 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +import { forOwn } from 'lodash'; import { SavedObject, SavedObjectConfig } from '../../types'; import { extractSearchSourceReferences } from '../../../../data/public'; import { expandShorthand } from './field_mapping'; @@ -17,7 +17,7 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje const attributes = {} as Record; const references = []; - _.forOwn(mapping, (fieldMapping, fieldName) => { + forOwn(mapping, (fieldMapping, fieldName) => { if (typeof fieldName !== 'string') { return; } diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index c4cee25a33b7ff..014142a2a3d068 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -47,139 +47,137 @@ exports[`TelemetryManagementSectionComponent does not show the endpoint link whe exports[`TelemetryManagementSectionComponent renders as expected 1`] = ` - - - - -

- -

-
-
-
- + <_EuiSplitPanelInner + color="subdued" + > + +

- - , - } - } + defaultMessage="Usage Data" + id="telemetry.usageDataTitle" + values={Object {}} /> -

- } - /> - - -

- - - , - } +

+
+ + <_EuiSplitPanelInner> + + + + , } - /> -

-

- - - , - "endpointSecurityData": - - , + } + /> +

+ } + /> + + +

+ + + , + } } - } - /> -

- , - "displayName": "Provide usage statistics", - "isCustom": true, - "isOverridden": false, - "name": "telemetry:enabled", - "requiresPageReload": false, - "type": "boolean", - "value": true, + /> +

+

+ + + , + "endpointSecurityData": + + , + } + } + /> +

+ , + "displayName": "Provide usage statistics", + "isCustom": true, + "isOverridden": false, + "name": "telemetry:enabled", + "requiresPageReload": false, + "type": "boolean", + "value": true, + } } - } - toasts={ - Object { - "add": [MockFunction], - "addDanger": [MockFunction], - "addError": [MockFunction], - "addInfo": [MockFunction], - "addSuccess": [MockFunction], - "addWarning": [MockFunction], - "get$": [MockFunction], - "remove": [MockFunction], + toasts={ + Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + } } - } - /> + /> +
-
+
`; diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx index e9ddc4cf82dfc7..b0d1b42a9b8927 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx @@ -7,16 +7,7 @@ */ import React, { Component, Fragment } from 'react'; -import { - EuiCallOut, - EuiPanel, - EuiForm, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiCallOut, EuiForm, EuiLink, EuiSpacer, EuiSplitPanel, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -116,46 +107,46 @@ export class TelemetryManagementSection extends Component { )} - + - - - -

- -

-
-
-
+ + +

+ +

+
+
- {this.maybeGetAppliesSettingMessage()} - - + + {this.maybeGetAppliesSettingMessage()} + + +
-
+ ); } diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts index 6a83dd323b3fdb..b64fcc383a1bb9 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts @@ -25,7 +25,7 @@ export class FieldNotFoundError extends Error { return this.constructor.name; } - public get body() { + public get errBody() { return this.message; } } diff --git a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts b/src/plugins/vis_type_timeseries/common/validate_interval.ts similarity index 52% rename from src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts rename to src/plugins/vis_type_timeseries/common/validate_interval.ts index a602b34d999867..7f9ccf20c0eb1d 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts +++ b/src/plugins/vis_type_timeseries/common/validate_interval.ts @@ -7,20 +7,29 @@ */ import { i18n } from '@kbn/i18n'; -import { GTE_INTERVAL_RE } from '../../../common/interval_regexp'; -import { search } from '../../../../../plugins/data/public'; +import { GTE_INTERVAL_RE } from './interval_regexp'; +import { parseInterval, TimeRangeBounds } from '../../data/common'; -import type { TimeRangeBounds } from '../../../../data/common'; -import type { TimeseriesVisParams } from '../../types'; +export class ValidateIntervalError extends Error { + constructor() { + super( + i18n.translate('visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage', { + defaultMessage: + 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.', + }) + ); + } + + public get name() { + return this.constructor.name; + } -const { parseInterval } = search.aggs; + public get errBody() { + return this.message; + } +} -export function validateInterval( - bounds: TimeRangeBounds, - panel: TimeseriesVisParams, - maxBuckets: number -) { - const { interval } = panel; +export function validateInterval(bounds: TimeRangeBounds, interval: string, maxBuckets: number) { const { min, max } = bounds; // No need to check auto it will return around 100 if (!interval) return; @@ -33,15 +42,7 @@ export function validateInterval( const span = max!.valueOf() - min!.valueOf(); const buckets = Math.floor(span / duration.asMilliseconds()); if (buckets > maxBuckets) { - throw new Error( - i18n.translate( - 'visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage', - { - defaultMessage: - 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.', - } - ) - ); + throw new ValidateIntervalError(); } } } diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx index 5391bf319ee573..d97100a0cfaaf1 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx @@ -18,8 +18,6 @@ import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { PersistedState } from 'src/plugins/visualizations/public'; import { PaletteRegistry } from 'src/plugins/charts/public'; -// @ts-expect-error -import { ErrorComponent } from './error'; import { TimeseriesVisTypes } from './vis_types'; import type { TimeseriesVisData, PanelData } from '../../../common/types'; import { isVisSeriesData } from '../../../common/vis_data_utils'; @@ -147,16 +145,6 @@ function TimeseriesVisualization({ handlers.done(); }); - // Show the error panel - const error = isVisSeriesData(visData) && visData[model.id]?.error; - if (error) { - return ( -
- -
- ); - } - const VisComponent = TimeseriesVisTypes[model.type]; const isLastValueMode = diff --git a/src/plugins/vis_type_timeseries/public/application/components/use_index_patter_mode_callout.tsx b/src/plugins/vis_type_timeseries/public/application/components/use_index_patter_mode_callout.tsx new file mode 100644 index 00000000000000..6191df2ecce5b8 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/use_index_patter_mode_callout.tsx @@ -0,0 +1,69 @@ +/* + * 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, { useMemo, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { EuiButton, EuiCallOut, EuiFlexGroup, EuiLink } from '@elastic/eui'; +import { getCoreStart } from '../../services'; + +const LOCAL_STORAGE_KEY = 'TSVB_INDEX_PATTERN_CALLOUT_HIDDEN'; + +export const UseIndexPatternModeCallout = () => { + const [dismissed, setDismissed] = useLocalStorage(LOCAL_STORAGE_KEY, false); + const indexPatternModeLink = useMemo( + () => getCoreStart().docLinks.links.visualize.tsvbIndexPatternMode, + [] + ); + + const dismissNotice = useCallback(() => { + setDismissed(true); + }, [setDismissed]); + + if (dismissed) { + return null; + } + + return ( + + } + iconType="cheer" + size="s" + > +

+ + + + ), + }} + /> +

+ + + + + +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx index 99f25643284025..424b39feff8364 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx @@ -12,7 +12,6 @@ import uuid from 'uuid/v4'; import { share } from 'rxjs/operators'; import { isEqual, isEmpty, debounce } from 'lodash'; import { EventEmitter } from 'events'; - import type { IUiSettingsClient } from 'kibana/public'; import { Vis, @@ -35,6 +34,7 @@ import { VisPicker } from './vis_picker'; import { fetchFields, VisFields } from '../lib/fetch_fields'; import { getDataStart, getCoreStart } from '../../services'; import { TimeseriesVisParams } from '../../types'; +import { UseIndexPatternModeCallout } from './use_index_patter_mode_callout'; const VIS_STATE_DEBOUNCE_DELAY = 200; const APP_NAME = 'VisEditor'; @@ -159,8 +159,8 @@ export class VisEditor extends Component { - this.visDataSubject.next(visData); + onDataChange = (data: { visData?: TimeseriesVisData }) => { + this.visDataSubject.next(data?.visData); }; render() { @@ -182,6 +182,7 @@ export class VisEditor extends Component
+ {!this.props.vis.params.use_kibana_indexes && }
diff --git a/src/plugins/vis_type_timeseries/public/request_handler.ts b/src/plugins/vis_type_timeseries/public/request_handler.ts index 89b3cb3b6c5831..4cd297a597dfce 100644 --- a/src/plugins/vis_type_timeseries/public/request_handler.ts +++ b/src/plugins/vis_type_timeseries/public/request_handler.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { KibanaContext } from '../../data/public'; - import { getTimezone } from './application/lib/get_timezone'; -import { validateInterval } from './application/lib/validate_interval'; import { getUISettings, getDataStart, getCoreStart } from './services'; -import { MAX_BUCKETS_SETTING, ROUTES } from '../common/constants'; -import { TimeseriesVisParams } from './types'; +import { ROUTES } from '../common/constants'; + +import type { TimeseriesVisParams } from './types'; import type { TimeseriesVisData } from '../common/types'; +import type { KibanaContext } from '../../data/public'; interface MetricsRequestHandlerParams { input: KibanaContext | null; @@ -37,10 +36,6 @@ export const metricsRequestHandler = async ({ const parsedTimeRange = data.query.timefilter.timefilter.calculateBounds(input?.timeRange!); if (visParams && visParams.id && !visParams.isModelInvalid) { - const maxBuckets = config.get(MAX_BUCKETS_SETTING); - - validateInterval(parsedTimeRange, visParams, maxBuckets); - const untrackSearch = dataSearch.session.isCurrentSession(searchSessionId) && dataSearch.session.trackSearch({ diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index dd45812f4ebfce..817812a88ca987 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { Framework } from '../plugin'; -import type { TimeseriesVisData } from '../../common/types'; +import type { TimeseriesVisData, FetchedIndexPattern, Series } from '../../common/types'; import { PANEL_TYPES } from '../../common/enums'; import type { VisTypeTimeseriesVisDataRequest, @@ -20,6 +20,8 @@ import { getSeriesData } from './vis_data/get_series_data'; import { getTableData } from './vis_data/get_table_data'; import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings'; import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher'; +import { MAX_BUCKETS_SETTING } from '../../common/constants'; +import { getIntervalAndTimefield } from './vis_data/get_interval_and_timefield'; export async function getVisData( requestContext: VisTypeTimeseriesRequestHandlerContext, @@ -32,15 +34,41 @@ export async function getVisData( const esQueryConfig = await getEsQueryConfig(uiSettings); const promises = request.body.panels.map((panel) => { + const cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService, { + fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes), + }); const services: VisTypeTimeseriesRequestServices = { esQueryConfig, esShardTimeout, indexPatternsService, uiSettings, + cachedIndexPatternFetcher, searchStrategyRegistry: framework.searchStrategyRegistry, - cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService, { - fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes), - }), + buildSeriesMetaParams: async ( + index: FetchedIndexPattern, + useKibanaIndexes: boolean, + series?: Series + ) => { + /** This part of code is required to try to get the default timefield for string indices. + * The rest of the functionality available for Kibana indexes should not be active **/ + if (!useKibanaIndexes && index.indexPatternString) { + index = await cachedIndexPatternFetcher(index.indexPatternString, true); + } + + const maxBuckets = await uiSettings.get(MAX_BUCKETS_SETTING); + const { min, max } = request.body.timerange; + + return getIntervalAndTimefield( + panel, + index, + { + min, + max, + maxBuckets, + }, + series + ); + }, }; return panel.type === PANEL_TYPES.TABLE diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts index 0d1ca9cba022a7..62220e08b7e104 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts @@ -11,12 +11,17 @@ import { FetchedIndexPattern, Panel, Series } from '../../../common/types'; describe('getIntervalAndTimefield(panel, series)', () => { const index: FetchedIndexPattern = {} as FetchedIndexPattern; + const params = { + min: '2017-01-01T00:00:00Z', + max: '2017-01-01T01:00:00Z', + maxBuckets: 1000, + }; test('returns the panel interval and timefield', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = {} as Series; - expect(getIntervalAndTimefield(panel, index, series)).toEqual({ + expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ timeField: '@timestamp', interval: 'auto', }); @@ -30,7 +35,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { series_time_field: 'time', } as unknown) as Series; - expect(getIntervalAndTimefield(panel, index, series)).toEqual({ + expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts index 0e90dfe77e8145..b7a22abd825e05 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts @@ -5,13 +5,25 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import moment from 'moment'; import { AUTO_INTERVAL } from '../../../common/constants'; import { validateField } from '../../../common/fields_utils'; +import { validateInterval } from '../../../common/validate_interval'; import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; -export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern, series?: Series) { +interface IntervalParams { + min: string; + max: string; + maxBuckets: number; +} + +export function getIntervalAndTimefield( + panel: Panel, + index: FetchedIndexPattern, + { min, max, maxBuckets }: IntervalParams, + series?: Series +) { const timeField = (series?.override_index_pattern ? series.series_time_field : panel.time_field) || index.indexPattern?.timeFieldName; @@ -28,6 +40,15 @@ export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern maxBars = series.series_max_bars; } + validateInterval( + { + min: moment.utc(min), + max: moment.utc(max), + }, + interval, + maxBuckets + ); + return { maxBars, timeField, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts index 822331e0ca0d07..8d495d68eb6257 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts @@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n'; -// not typed yet -// @ts-expect-error import { handleErrorResponse } from './handle_error_response'; import { getAnnotations } from './get_annotations'; import { handleResponseBody } from './series/handle_response_body'; @@ -51,6 +49,8 @@ export async function getSeriesData( uiRestrictions: capabilities.uiRestrictions, }; + const handleError = handleErrorResponse(panel); + try { const bodiesPromises = getActiveSeries(panel).map((series) => getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services) @@ -97,14 +97,9 @@ export async function getSeriesData( }, }; } catch (err) { - if (err.body) { - err.response = err.body; - - return { - ...meta, - ...handleErrorResponse(panel)(err), - }; - } - return meta; + return { + ...meta, + ...handleError(err), + }; } } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index db2e027f7815c3..3f8d30f0ed8339 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -12,20 +12,19 @@ import { get } from 'lodash'; // not typed yet // @ts-expect-error import { buildRequestBody } from './table/build_request_body'; -// @ts-expect-error import { handleErrorResponse } from './handle_error_response'; // @ts-expect-error import { processBucket } from './table/process_bucket'; import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher'; import { extractFieldLabel } from '../../../common/fields_utils'; + import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRequestServices, VisTypeTimeseriesVisDataRequest, } from '../../types'; import type { Panel } from '../../../common/types'; -import { getIntervalAndTimefield } from './get_interval_and_timefield'; export async function getTableData( requestContext: VisTypeTimeseriesRequestHandlerContext, @@ -67,23 +66,13 @@ export async function getTableData( return panel.pivot_id; }; - const buildSeriesMetaParams = async () => { - let index = panelIndex; - - /** This part of code is required to try to get the default timefield for string indices. - * The rest of the functionality available for Kibana indexes should not be active **/ - if (!panel.use_kibana_indexes && index.indexPatternString) { - index = await services.cachedIndexPatternFetcher(index.indexPatternString, true); - } - - return getIntervalAndTimefield(panel, index); - }; - const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, }; + const handleError = handleErrorResponse(panel); + try { const body = await buildRequestBody( req, @@ -92,7 +81,7 @@ export async function getTableData( panelIndex, capabilities, services.uiSettings, - buildSeriesMetaParams + () => services.buildSeriesMetaParams(panelIndex, Boolean(panel.use_kibana_indexes)) ); const [resp] = await searchStrategy.search(requestContext, req, [ @@ -121,14 +110,9 @@ export async function getTableData( series, }; } catch (err) { - if (err.body) { - err.response = err.body; - - return { - ...meta, - ...handleErrorResponse(panel)(err), - }; - } - return meta; + return { + ...meta, + ...handleError(err), + }; } } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js deleted file mode 100644 index b5583ce20d68ae..00000000000000 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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. - */ - -export const handleErrorResponse = (panel) => (error) => { - if (error.isBoom && error.status === 401) throw error; - const result = {}; - let errorResponse; - try { - errorResponse = JSON.parse(error.response); - } catch (e) { - errorResponse = error.response; - } - if (!errorResponse && !(error.name === 'KQLSyntaxError')) { - errorResponse = { - message: error.message, - stack: error.stack, - }; - } - if (error.name === 'KQLSyntaxError') { - errorResponse = { - message: error.shortMessage, - stack: error.stack, - }; - } - result[panel.id] = { - id: panel.id, - statusCode: error.statusCode, - error: errorResponse, - series: [], - }; - return result; -}; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts new file mode 100644 index 00000000000000..eeb22a3dc32cff --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts @@ -0,0 +1,99 @@ +/* + * 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 { Panel } from '../../../common/types'; +import { handleErrorResponse, ErrorResponse } from './handle_error_response'; + +describe('handleErrorResponse', () => { + const handleError = handleErrorResponse(({ + id: 'test_panel', + } as unknown) as Panel); + + test('should only handle errors that contain errBody', () => { + expect(handleError(new Error('Test Error'))).toMatchInlineSnapshot(`Object {}`); + + expect(handleError({ errBody: 'test' } as ErrorResponse)).toMatchInlineSnapshot(` + Object { + "test_panel": Object { + "error": "test", + "id": "test_panel", + "series": Array [], + }, + } + `); + }); + + test('should set as error the last value of caused_by', () => { + expect( + handleError({ + errBody: { + error: { + reason: 'wrong 0', + caused_by: { + reason: 'wrong 1', + caused_by: { + caused_by: 'ok', + }, + }, + }, + }, + } as ErrorResponse) + ).toMatchInlineSnapshot(` + Object { + "test_panel": Object { + "error": "ok", + "id": "test_panel", + "series": Array [], + }, + } + `); + }); + + test('should use the previous error message if the actual value is empty', () => { + expect( + handleError({ + errBody: { + error: { + reason: 'ok', + caused_by: { + reason: '', + }, + }, + }, + } as ErrorResponse) + ).toMatchInlineSnapshot(` + Object { + "test_panel": Object { + "error": "ok", + "id": "test_panel", + "series": Array [], + }, + } + `); + }); + + test('shouldn not return empty error message', () => { + expect( + handleError({ + errBody: { + error: { + reason: '', + }, + }, + } as ErrorResponse) + ).toMatchInlineSnapshot(` + Object { + "test_panel": Object { + "error": "Unexpected error", + "id": "test_panel", + "series": Array [], + }, + } + `); + }); +}); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts new file mode 100644 index 00000000000000..d9327a0fbb786e --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts @@ -0,0 +1,57 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { Panel } from '../../../common/types'; + +type ErrorType = + | { + reason: string; + caused_by?: ErrorType; + } + | string; + +export type ErrorResponse = Error & + Partial<{ + errBody: + | { + error: ErrorType; + } + | string; + }>; + +const getErrorMessage = (errBody: ErrorType, defaultMessage?: string): string | undefined => { + if (typeof errBody === 'string') { + return errBody; + } else { + if (errBody.caused_by) { + return getErrorMessage(errBody.caused_by, errBody.reason); + } + return errBody.reason || defaultMessage; + } +}; + +export const handleErrorResponse = (panel: Panel) => (error: ErrorResponse) => { + const result: Record = {}; + + if (error.errBody) { + const errorResponse = + typeof error.errBody === 'string' ? error.errBody : getErrorMessage(error.errBody.error); + + result[panel.id] = { + id: panel.id, + error: + errorResponse ?? + i18n.translate('visTypeTimeseries.handleErrorResponse.unexpectedError', { + defaultMessage: 'Unexpected error', + }), + series: [], + }; + } + + return result; +}; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js index b9ce76f7176b45..ad20f434bedf54 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js @@ -45,8 +45,6 @@ const calculateBucketData = (timeInterval, capabilities) => { if (converted) { intervalString = converted.value + converted.unit; } - - intervalString = undefined; } else { intervalString = '1ms'; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js index de93ff22fa5980..e2211e4843bd68 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js @@ -15,6 +15,6 @@ export { getLastMetric } from './get_last_metric'; export { getSiblingAggValue } from './get_sibling_agg_value'; export { getSplits } from './get_splits'; export { getTimerange } from './get_timerange'; -export { mapBucket } from './map_bucket'; export { parseSettings } from './parse_settings'; +export { mapEmptyToZero } from './map_empty_to_zero'; export { overwrite } from './overwrite'; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts similarity index 53% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts index 63ba294b7dc46a..b5f1adc3f02022 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts @@ -6,39 +6,47 @@ * Side Public License, v 1. */ -import { mapBucket } from './map_bucket'; +import { mapEmptyToZero } from './map_empty_to_zero'; -describe('mapBucket(metric)', () => { +describe('mapEmptyToZero(metric, buckets)', () => { test('returns bucket key and value for basic metric', () => { const metric = { id: 'AVG', type: 'avg' }; - const bucket = { - key: 1234, - AVG: { value: 1 }, - }; - expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); + const buckets = [ + { + key: 1234, + AVG: { value: 1 }, + }, + ]; + expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]); }); test('returns bucket key and value for std_deviation', () => { const metric = { id: 'STDDEV', type: 'std_deviation' }; - const bucket = { - key: 1234, - STDDEV: { std_deviation: 1 }, - }; - expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); + const buckets = [ + { + key: 1234, + STDDEV: { std_deviation: 1 }, + }, + ]; + expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]); }); test('returns bucket key and value for percentiles', () => { const metric = { id: 'PCT', type: 'percentile', percent: 50 }; - const bucket = { - key: 1234, - PCT: { values: { '50.0': 1 } }, - }; - expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); + const buckets = [ + { + key: 1234, + PCT: { values: { '50.0': 1 } }, + }, + ]; + expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]); }); test('returns bucket key and value for derivative', () => { const metric = { id: 'DERV', type: 'derivative', field: 'io', unit: '1s' }; - const bucket = { - key: 1234, - DERV: { value: 100, normalized_value: 1 }, - }; - expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); + const buckets = [ + { + key: 1234, + DERV: { value: 100, normalized_value: 1 }, + }, + ]; + expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts new file mode 100644 index 00000000000000..0490193a76e81a --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +// @ts-expect-error not typed yet +import { getAggValue } from './get_agg_value'; +import { METRIC_TYPES } from '../../../../../data/common'; +import type { Metric } from '../../../../common/types'; + +export const mapEmptyToZero = (metric: Metric, buckets: any[]) => { + // Metric types where an empty set equals `zero` + const isSettableToZero = [ + METRIC_TYPES.COUNT, + METRIC_TYPES.CARDINALITY, + METRIC_TYPES.SUM, + ].includes(metric.type as METRIC_TYPES); + + return isSettableToZero && !buckets.length + ? [[undefined, 0]] + : buckets.map((bucket) => [bucket.key, getAggValue(bucket, metric)]); +}; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 08b9801254c2e2..022718ece435d9 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -47,7 +47,16 @@ describe('dateHistogram(req, panel, series)', () => { get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50), }; buildSeriesMetaParams = jest.fn(async () => { - return getIntervalAndTimefield(panel, indexPattern, series); + return getIntervalAndTimefield( + panel, + indexPattern, + { + min: '2017-01-01T00:00:00Z', + max: '2017-01-01T01:00:00Z', + maxBuckets: 1000, + }, + series + ); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js index d3cff76524ee36..a6addc8ba0e53b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js @@ -10,10 +10,7 @@ import { convertIntervalToUnit } from '../../helpers/unit_to_seconds'; const percentileValueMatch = /\[([0-9\.]+)\]$/; import { startsWith, flatten, values, first, last } from 'lodash'; -import { getDefaultDecoration } from '../../helpers/get_default_decoration'; -import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value'; -import { getSplits } from '../../helpers/get_splits'; -import { mapBucket } from '../../helpers/map_bucket'; +import { getDefaultDecoration, getSiblingAggValue, getSplits, mapEmptyToZero } from '../../helpers'; import { evaluate } from '@kbn/tinymath'; export function mathAgg(resp, panel, series, meta, extractFields) { @@ -44,7 +41,7 @@ export function mathAgg(resp, panel, series, meta, extractFields) { } else { const percentileMatch = v.field.match(percentileValueMatch); const m = percentileMatch ? { ...metric, percent: percentileMatch[1] } : { ...metric }; - acc[v.name] = split.timeseries.buckets.map(mapBucket(m)); + acc[v.name] = mapEmptyToZero(m, split.timeseries.buckets); } return acc; }, {}); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js index b9a4139a723138..cc406041ad8742 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { getDefaultDecoration } from '../../helpers/get_default_decoration'; -import { getSplits } from '../../helpers/get_splits'; -import { getLastMetric } from '../../helpers/get_last_metric'; -import { mapBucket } from '../../helpers/map_bucket'; +import { getDefaultDecoration, getSplits, getLastMetric, mapEmptyToZero } from '../../helpers'; import { METRIC_TYPES } from '../../../../../common/enums'; export function stdMetric(resp, panel, series, meta, extractFields) { @@ -26,7 +23,7 @@ export function stdMetric(resp, panel, series, meta, extractFields) { const decoration = getDefaultDecoration(series); (await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => { - const data = split.timeseries.buckets.map(mapBucket(metric)); + const data = mapEmptyToZero(metric, split.timeseries.buckets); results.push({ id: `${split.id}`, label: split.label, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js index a648258745a15e..140212c2ec9070 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import { getSplits } from '../../helpers/get_splits'; -import { getLastMetric } from '../../helpers/get_last_metric'; -import { mapBucket } from '../../helpers/map_bucket'; +import { getSplits, getLastMetric, mapEmptyToZero } from '../../helpers'; import { METRIC_TYPES } from '../../../../../common/enums'; export function stdMetric(bucket, panel, series, meta, extractFields) { @@ -32,7 +30,7 @@ export function stdMetric(bucket, panel, series, meta, extractFields) { }; (await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => { - const data = split.timeseries.buckets.map(mapBucket(metric)); + const data = mapEmptyToZero(metric, split.timeseries.buckets); results.push({ id: split.id, label: split.label, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts index a2248308dc5711..aedc4ee7c8a155 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts @@ -7,7 +7,6 @@ */ import { buildRequestBody } from './build_request_body'; -import { getIntervalAndTimefield } from '../get_interval_and_timefield'; import type { FetchedIndexPattern, Panel, Series } from '../../../../common/types'; import type { @@ -27,6 +26,7 @@ export async function getSeriesRequestParams( esShardTimeout, uiSettings, cachedIndexPatternFetcher, + buildSeriesMetaParams, }: VisTypeTimeseriesRequestServices ) { let seriesIndex = panelIndex; @@ -35,18 +35,6 @@ export async function getSeriesRequestParams( seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? ''); } - const buildSeriesMetaParams = async () => { - let index = seriesIndex; - - /** This part of code is required to try to get the default timefield for string indices. - * The rest of the functionality available for Kibana indexes should not be active **/ - if (!panel.use_kibana_indexes && index.indexPatternString) { - index = await cachedIndexPatternFetcher(index.indexPatternString, true); - } - - return getIntervalAndTimefield(panel, index, series); - }; - const request = await buildRequestBody( req, panel, @@ -55,7 +43,7 @@ export async function getSeriesRequestParams( seriesIndex, capabilities, uiSettings, - buildSeriesMetaParams + () => buildSeriesMetaParams(seriesIndex, Boolean(panel.use_kibana_indexes), series) ); return { diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts index 2fc46b7cd1f11c..a2657f99d222dc 100644 --- a/src/plugins/vis_type_timeseries/server/types.ts +++ b/src/plugins/vis_type_timeseries/server/types.ts @@ -6,17 +6,18 @@ * Side Public License, v 1. */ -import { Observable } from 'rxjs'; -import { SharedGlobalConfig } from 'kibana/server'; +import type { Observable } from 'rxjs'; +import type { SharedGlobalConfig } from 'kibana/server'; import type { IRouter, IUiSettingsClient, KibanaRequest } from 'src/core/server'; import type { DataRequestHandlerContext, EsQueryConfig, IndexPatternsService, } from '../../data/server'; -import type { VisPayload } from '../common/types'; +import type { Series, VisPayload } from '../common/types'; import type { SearchStrategyRegistry } from './lib/search_strategies'; import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher'; +import type { FetchedIndexPattern } from '../common/types'; export type ConfigObservable = Observable; @@ -35,4 +36,13 @@ export interface VisTypeTimeseriesRequestServices { indexPatternsService: IndexPatternsService; searchStrategyRegistry: SearchStrategyRegistry; cachedIndexPatternFetcher: CachedIndexPatternFetcher; + buildSeriesMetaParams: ( + index: FetchedIndexPattern, + useKibanaIndexes: boolean, + series?: Series + ) => Promise<{ + maxBars: number; + timeField?: string; + interval: string; + }>; } diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index ae04fe5d2b9390..3f47c6155f1753 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -312,9 +312,12 @@ export class TestSubjects extends FtrService { return testSubjSelector(selector); } - public async scrollIntoView(selector: string) { + public async scrollIntoView( + selector: string, + offset?: number | { topOffset?: number; bottomOffset?: number } + ) { const element = await this.find(selector); - await element.scrollIntoViewIfNecessary(); + await element.scrollIntoViewIfNecessary(offset); } // isChecked always returns false when run on an euiSwitch, because they use the aria-checked attribute diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh index 40bfc6e83ad1bf..5c0ca5a8e01ca1 100755 --- a/test/scripts/jenkins_baseline.sh +++ b/test/scripts/jenkins_baseline.sh @@ -4,7 +4,7 @@ source src/dev/ci_setup/setup_env.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting OSS Kibana distributable for use in functional tests" -node scripts/build --debug --oss +node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 198723908cf487..a9edd3ed2a701b 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -33,7 +33,7 @@ node x-pack/scripts/functional_tests --assert-none-excluded \ # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug --no-oss + node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index 5571eee4f28edb..d7c7bda83c9ef2 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -60,7 +60,7 @@ export KBN_NP_PLUGINS_BUILT=true echo " -> Building and extracting default Kibana distributable for use in functional tests" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index 8d5624949505a5..c68c0f40902f22 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -5,7 +5,7 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting default Kibana distributable" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_xpack_package_build.sh b/test/scripts/jenkins_xpack_package_build.sh index 698129a2d253bd..86e846f720803f 100755 --- a/test/scripts/jenkins_xpack_package_build.sh +++ b/test/scripts/jenkins_xpack_package_build.sh @@ -7,6 +7,6 @@ source src/dev/ci_setup/setup_env.sh export TMP=/tmp export TMPDIR=/tmp -node scripts/build --all-platforms --debug --no-oss +node scripts/build --all-platforms --debug gsutil -q -m cp 'target/*' "gs://ci-artifacts.kibana.dev/package-testing/$GIT_COMMIT/" diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index a13ddbbd79ef07..bf43e200b902d3 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -17,6 +17,7 @@ import { EuiPageHeader, EuiPageHeaderSection, EuiTitle, + EuiCallOut, } from '@elastic/eui'; import { IndexPattern } from 'src/plugins/data/public'; import { CoreStart } from 'kibana/public'; @@ -149,6 +150,7 @@ export const App = (props: { { // eslint-disable-next-line no-bitwise @@ -177,12 +179,32 @@ export const App = (props: { setColor(newColor); }} > - Edit in Lens + Edit in Lens (new tab) + + + + { + props.plugins.lens.navigateToPrefilledEditor( + { + id: '', + timeRange: time, + attributes: getLensAttributes(props.defaultIndexPattern!, color), + }, + false + ); + }} + > + Edit in Lens (same tab) { setIsSaveModalVisible(true); @@ -191,6 +213,21 @@ export const App = (props: { Save Visualization + + { + setTime({ + from: '2015-09-18T06:31:44.000Z', + to: '2015-09-23T18:31:44.000Z', + }); + }} + > + Change time range + + {}} onClose={() => setIsSaveModalVisible(false)} /> )} ) : ( -

This demo only works if your default index pattern is set and time based

+ +

This demo only works if your default index pattern is set and time based

+
)} diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 65b28015f7f932..2c5287525c5974 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -152,7 +152,7 @@ export class ActionsPlugin implements Plugin()) diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index df63625bf242d2..b906983017ff60 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -153,7 +153,7 @@ export class AlertingPlugin { constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.create().pipe(first()).toPromise(); - this.logger = initializerContext.logger.get('plugins', 'alerting'); + this.logger = initializerContext.logger.get(); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); this.alertingAuthorizationClientFactory = new AlertingAuthorizationClientFactory(); diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap index 71b0929164705b..a2baee60749897 100644 --- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap @@ -16,6 +16,9 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "dotnet": { "type": "long" }, + "iOS/swift": { + "type": "long" + }, "go": { "type": "long" }, @@ -70,6 +73,9 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "opentelemetry/ruby": { "type": "long" }, + "opentelemetry/swift": { + "type": "long" + }, "opentelemetry/webjs": { "type": "long" } @@ -131,6 +137,60 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the } } }, + "iOS/swift": { + "properties": { + "agent": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "composite": { + "type": "keyword" + } + } + }, + "language": { + "properties": { + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "composite": { + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + }, + "composite": { + "type": "keyword" + } + } + } + } + } + } + }, "go": { "properties": { "agent": { diff --git a/x-pack/plugins/apm/common/agent_name.test.ts b/x-pack/plugins/apm/common/agent_name.test.ts index 9f74136efe829c..162a5716d6c7b1 100644 --- a/x-pack/plugins/apm/common/agent_name.test.ts +++ b/x-pack/plugins/apm/common/agent_name.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isJavaAgentName, isRumAgentName } from './agent_name'; +import { isJavaAgentName, isRumAgentName, isIosAgentName } from './agent_name'; describe('agent name helpers', () => { describe('isJavaAgentName', () => { @@ -22,7 +22,7 @@ describe('agent name helpers', () => { }); describe('when the agent name is not java', () => { - it('returns true', () => { + it('returns false', () => { expect(isJavaAgentName('not java')).toEqual(false); }); }); @@ -47,9 +47,35 @@ describe('agent name helpers', () => { }); }); - describe('when the agent name something else', () => { + describe('when the agent name is something else', () => { + it('returns false', () => { + expect(isRumAgentName('not rum')).toEqual(false); + }); + }); + }); + + describe('isIosAgentName', () => { + describe('when the agent name is js-base', () => { + it('returns true', () => { + expect(isIosAgentName('iOS/swift')).toEqual(true); + }); + }); + + describe('when the agent name is rum-js', () => { it('returns true', () => { - expect(isRumAgentName('java')).toEqual(false); + expect(isIosAgentName('ios/swift')).toEqual(true); + }); + }); + + describe('when the agent name is opentelemetry/swift', () => { + it('returns true', () => { + expect(isIosAgentName('opentelemetry/swift')).toEqual(true); + }); + }); + + describe('when the agent name is something else', () => { + it('returns false', () => { + expect(isIosAgentName('not ios')).toEqual(false); }); }); }); diff --git a/x-pack/plugins/apm/common/agent_name.ts b/x-pack/plugins/apm/common/agent_name.ts index 36bfbabf7797d0..650e72751749e5 100644 --- a/x-pack/plugins/apm/common/agent_name.ts +++ b/x-pack/plugins/apm/common/agent_name.ts @@ -26,12 +26,14 @@ export const OPEN_TELEMETRY_AGENT_NAMES: AgentName[] = [ 'opentelemetry/php', 'opentelemetry/python', 'opentelemetry/ruby', + 'opentelemetry/swift', 'opentelemetry/webjs', ]; export const AGENT_NAMES: AgentName[] = [ 'dotnet', 'go', + 'iOS/swift', 'java', 'js-base', 'nodejs', @@ -62,7 +64,9 @@ export function isRumAgentName( return RUM_AGENT_NAMES.includes(agentName! as AgentName); } -export function normalizeAgentName(agentName: string | undefined) { +export function normalizeAgentName( + agentName: T +): T | string { if (isRumAgentName(agentName)) { return 'rum-js'; } @@ -71,5 +75,14 @@ export function normalizeAgentName(agentName: string | undefined) { return 'java'; } + if (isIosAgentName(agentName)) { + return 'ios'; + } + return agentName; } + +export function isIosAgentName(agentName?: string) { + const lowercased = agentName && agentName.toLowerCase(); + return lowercased === 'ios/swift' || lowercased === 'opentelemetry/swift'; +} diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx index 47e83fa079e638..9900093253d2a4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/schema/confirm_switch_modal.tsx @@ -57,7 +57,7 @@ export function ConfirmSwitchModal({

{i18n.translate('xpack.apm.settings.schema.confirm.descriptionText', { defaultMessage: - 'If you have custom dashboards, machine learning jobs, or source maps that use classic APM indices, you must reconfigure them for data streams. Stack monitoring is not currently supported with Fleet-managed APM.', + 'Please note Stack monitoring is not currently supported with Fleet-managed APM.', })}

{!hasUnsupportedConfigs && ( diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx index 7fb7444a52f848..526aad56e743ec 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx @@ -248,6 +248,7 @@ function ErrorTimeseriesChart({ yAccessors={['y']} data={overallData?.overall?.timeseries ?? []} curve={CurveType.CURVE_MONOTONE_X} + color={theme.eui.euiColorVis7} /> {correlationsData && selectedSignificantTerm ? ( diff --git a/x-pack/plugins/apm/public/components/app/correlations/index.tsx b/x-pack/plugins/apm/public/components/app/correlations/index.tsx index 7b6328916d445e..36b298af834ac1 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/index.tsx @@ -131,6 +131,7 @@ export function Correlations() { return ( <> { setIsFlyoutVisible(true); }} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 5b202e208a52d9..fce543b05c6c3b 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; -import { isRumAgentName } from '../../../../common/agent_name'; +import { isRumAgentName, isIosAgentName } from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; @@ -43,6 +43,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) { const { isMedium } = useBreakPoints(); const rowDirection = isMedium ? 'column' : 'row'; const isRumAgent = isRumAgentName(agentName); + const isIosAgent = isIosAgentName(agentName); return ( @@ -110,7 +111,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) { )} - {!isRumAgent && ( + {!isRumAgent && !isIosAgent && ( [0] & { key: @@ -54,12 +70,12 @@ interface Props { export function ApmServiceTemplate(props: Props) { return ( -