diff --git a/.buildkite/scripts/steps/check_types_commits.sh b/.buildkite/scripts/steps/check_types_commits.sh index 36ea6299194e9..2fe1af46825fb 100755 --- a/.buildkite/scripts/steps/check_types_commits.sh +++ b/.buildkite/scripts/steps/check_types_commits.sh @@ -2,7 +2,6 @@ set -euo pipefail - if [[ "${CI-}" == "true" ]]; then .buildkite/scripts/bootstrap.sh @@ -106,10 +105,12 @@ done echo "Looking for related tsconfig.json files..." -for dir in "${uniq_dirs[@]}" -do - find_tsconfig $dir -done +if [ ${#uniq_dirs[@]} -gt 0 ]; then + for dir in "${uniq_dirs[@]}" + do + find_tsconfig $dir + done +fi if [ ${#uniq_tsconfigs[@]} -eq 0 ]; then if [[ "$sha1" == "--cached" ]]; then @@ -122,7 +123,17 @@ fi echo "Running scripts/type_check for each found tsconfig.json file..." +finalExitCode=0 + for tsconfig in "${uniq_tsconfigs[@]}" do + set +e node scripts/type_check --project $tsconfig -done \ No newline at end of file + exitCode=$? + set -e + if [ "$exitCode" -gt "$finalExitCode" ]; then + finalExitCode=$exitCode + fi +done + +exit $finalExitCode diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9a6a585abd68c..b443075cf7555 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -388,6 +388,7 @@ src/plugins/chart_expressions/expression_xy @elastic/kibana-visualizations examples/expressions_explorer @elastic/kibana-app-services src/plugins/expressions @elastic/kibana-visualizations packages/kbn-failed-test-reporter-cli @elastic/kibana-operations @elastic/appex-qa +examples/feature_control_examples @elastic/kibana-security x-pack/test/plugin_api_integration/plugins/feature_usage_test @elastic/kibana-security x-pack/plugins/features @elastic/kibana-core x-pack/test/functional_execution_context/plugins/alerts @elastic/kibana-core diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 7e4e5547efb1f..01a9129ce7e82 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -5,6 +5,7 @@ xpack.apm.enabled: false xpack.cloudSecurityPosture.enabled: false xpack.infra.enabled: false +xpack.observabilityLogExplorer.enabled: false xpack.observability.enabled: false xpack.securitySolution.enabled: false xpack.serverless.observability.enabled: false diff --git a/config/serverless.security.yml b/config/serverless.security.yml index 7a186ac9e70b1..19927adb58660 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -4,6 +4,7 @@ enterpriseSearch.enabled: false xpack.apm.enabled: false xpack.infra.enabled: false +xpack.observabilityLogExplorer.enabled: false xpack.observability.enabled: false xpack.uptime.enabled: false xpack.legacy_uptime.enabled: false diff --git a/dev_docs/assets/feature_privileges_example_plugin.png b/dev_docs/assets/feature_privileges_example_plugin.png new file mode 100644 index 0000000000000..46e0bcc457cf8 Binary files /dev/null and b/dev_docs/assets/feature_privileges_example_plugin.png differ diff --git a/dev_docs/key_concepts/feature_privileges.mdx b/dev_docs/key_concepts/feature_privileges.mdx new file mode 100644 index 0000000000000..7666ca1e82399 --- /dev/null +++ b/dev_docs/key_concepts/feature_privileges.mdx @@ -0,0 +1,451 @@ +--- +id: kibDevFeaturePrivileges +slug: /kibana-dev-docs/key-concepts/designing-feature-privileges +title: Creating Feature Privileges +description: Learn to control access to features of your plugin +date: 2023-09-18 +tags: ['kibana', 'dev', 'architecture', 'contributor'] +--- + +If you've added a new feature to Kibana and would like administrators to have granular control over who can access and perform actions within this feature, then creating Feature privileges is what you're after. + +Feature Privileges allow you to define precisely who can access your feature and what actions they can perform, all while seamlessly integrating with Kibana's role-based access control (RBAC) system. +This documentation will guide you through the process of creating and configuring Feature Privileges for your Kibana plugin or features. Whether you're building a new visualization, enhancing an existing application, or extending Kibana's capabilities, understanding how to set up Feature Privileges is vital to maintaining security, control, and user satisfaction. + +By the end of this guide, you'll know how to: + +- Define privileges for your feature, specifying who can access it. +- Control access to your feature's user interface (UI) elements. +- Secure server-side APIs, ensuring that only authorized users can interact with your plugin's functionality. + +### Registering features + +Kibana has an inbuilt Features plugin that enables you to control access to your plugin and its features. To start, ensure that your kibana.jsonc config file has the `features` plugin inside the requiredPlugins list + +``` + "requiredPlugins": [ + ..., + "features", // required + ] +``` + +This will give you access to the contract exposed by the features plugin inside the setup dependencies of your own plugin. + +Now, inside your plugin, you can use the contract to control access by first registering the features that your plugin has + +```ts +const FEATURE_PRIVILEGES_PLUGIN_ID = 'MY_PLUGIN_IDENTIFIER'; +features.registerKibanaFeature({ + id: FEATURE_PRIVILEGES_PLUGIN_ID, + name: 'Feature Plugin Examples', + category: DEFAULT_APP_CATEGORIES.management, + app: ['FeaturePluginExample'], + privileges: { + all: { + app: ['FeaturePluginExample'], + }, + read: { + app: ['FeaturePluginExample'], + }, + }, +}); +``` + +### Control access to UI elements + +![image](../assets/feature_privileges_example_plugin.png) + +With regards to feature privileges, we control access to UI elements by declaring UI capabilities. + +UI capabilities determine what actions users are allowed to perform within the UI of your plugin. These are often associated with specific features inside your app and can be finely tuned to control access at a granular level. + +Let's take a basic example of UI capabilities for our example plugin. Lets say that our plugin allows the following user actions in general: + +`view, create, edit, delete, assign`. + +Now we'd like to ensure that only users with the `all` privilege are granted access to all actions whereas users with the `read` privilege can only view the resources in your app. + +To do so, we modify the feature registration config above and include the following entries in the `privileges.all` and `privileges.read` sections respectively + +```ts +{ + ..., + privileges: { + all: { + ..., + ui: ['view', 'create', 'edit', 'delete', 'assign'], + }, + read: { + ..., + ui: ['view'], + } + } +} +``` + +Once done, you can now access this within the public part kibana plugin code using the Kibana Context Provider as follows: + +```tsx +export const MyPluginComponent: React.FC = () => { + const kibana = useKibana(); + + return ( + + + +

Feature Privileges Example

+
+
+ + +

Your privileges

+
+ + {Object.entries( + kibana.services.application!.capabilities[FEATURE_PRIVILEGES_PLUGIN_ID] + ).map(([capability, value]) => { + return value === true ? ( +
+ {capability} + +
+ ) : null; + })} +
+
+ ); +}; +``` + +### Control access to API endpoints and other server side features + +Similarly for API access, lets modify the server side of our plugin by adding a couple of API routes as follows: + +```ts +public setup(core: CoreSetup, deps: FeatureControlExampleDeps) { + // ... + const router = core.http.createRouter(); + router.get( + { + path: '/internal/my_plugin/read', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); + router.get( + { + path: '/internal/my_plugin/sensitive_action', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new SecretClass().secretResponse(), + }, + }); + } + ); +} +``` + +Here we've added two routes, one which returns the current date-time in ISO 8601 format and another which returns a sensitive response that we'd like to protect. + +To do so, we need to modify the configuration object for our route to look like this: + +```ts +public setup(core: CoreSetup, deps: FeatureControlExampleDeps) { + // ... + const router = core.http.createRouter(); + router.get( + { + path: '/internal/my_plugin/read', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); + router.get( + { + path: '/internal/my_plugin/sensitive_action', + validate: false, + options: { + tags: ['access:my_closed_example_api'], + }, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new SecretClass().secretResponse(), + }, + }); + } + ); +} +``` + +Notice, we've added an `options.tags` property for the API route that returns sensitive information. This tag is then used in the privileges object as follow + +```ts +{ + …, + privileges: { + all: { + ..., + api: ['my_closed_example_api'], // Notice that we've dropped the `access` string here + }, + read: { + ..., + api: [], + } + } +} +``` + +This tells the Kibana RBAC system that users with the role that has `all` access to your plugin can also access the api that is tagged with `my_closed_example_api`. This now secures your API endpoint and will throw a `403 Unauthorized` error to any users who don't have the right permissions. + +We've successfully added access control to your plugin using the Kibana feature privileges. + +## Options and Configuration + +A deep dive into every option for the Kibana Feature configuration and what they mean. + +### KibanaFeatureConfig Interface + +#### id + +- **Description**: Unique identifier for this feature. This identifier is also used when generating UI Capabilities. +- **Type**: string +- **Example**: `"myFeature"` + +#### name + +- **Description**: Display name for this feature. This will be displayed to end-users, so a translatable string is advised for i18n. +- **Type**: string +- **Example**: `"My Feature"` + +#### description (optional) + +- **Description**: An optional description that will appear as subtext underneath the feature name. +- **Type**: string +- **Example**: `"This is a feature that allows users to perform advanced actions."` + +#### category + +- **Description**: The category for this feature. This will be used to organize the list of features for display within the Spaces and Roles management screens. +- **Type**: AppCategory (enum) +- **Example**: `AppCategory.Security` + +#### order (optional) + +- **Description**: An ordinal used to sort features relative to one another for display. +- **Type**: number +- **Example**: `10` + +#### excludeFromBasePrivileges (optional) + +- **Description**: Whether or not this feature should be excluded from the base privileges. This is primarily helpful when migrating applications with a "legacy" privileges model to use Kibana privileges. +- **Type**: boolean +- **Example**: `false` + +#### minimumLicense (optional) + +- **Description**: Optional minimum supported license. If omitted, all licenses are allowed. This does not restrict access to your feature based on license. Its only purpose is to inform the space and roles UIs on which features to display. +- **Type**: LicenseType (enum) +- **Example**: `LicenseType.Gold` + +#### app + +- **Description**: An array of app IDs that are enabled when this feature is enabled. Apps specified here will automatically cascade to the privileges defined below, unless specified differently there. +- **Type**: string[] +- **Example**: `["app1", "app2"]` + +#### management (optional) + +- **Description**: If this feature includes management sections, you can specify them here to control visibility of those pages based on the current space. +- **Type**: Object +- **Example**: + ```js + management: { + kibana: ["settings", "indices"], + myApp: ["config"] + } + ``` + +#### catalogue (optional) + +- **Description**: If this feature includes a catalogue entry, you can specify them here to control visibility based on the current space. +- **Type**: string[] +- **Example**: `["cat1", "cat2"]` + +#### alerting (optional) + +- **Description**: If your feature grants access to specific Alert Types, you can specify them here to control visibility based on the current space. +- **Type**: string[] +- **Example**: `["alertType1", "alertType2"]` + +#### cases (optional) + +- **Description**: If your feature grants access to specific case types, you can specify them here to control visibility based on the current space. +- **Type**: string[] +- **Example**: `["caseType1", "caseType2"]` + +#### privileges + +- **Description**: Feature privilege definition. These are used to control what access the read/all roles will have when enabled for your application. +- **Type**: [FeatureKibanaPrivileges](#featurekibanaprivileges-interface) +- **Example**: + ```js + privileges: { + all: {...}, // expanded below + read: {...} + } + ``` + +#### subFeatures (optional) + +- **Description**: Optional sub-feature privilege definitions. This can only be specified if `privileges` are also defined. +- **Type**: SubFeatureConfig[] +- **Example**: + ```js + subFeatures: [ + { + id: 'subFeature1', + name: 'Sub Feature 1', + // ... other sub-feature properties + }, + ]; + ``` + +#### privilegesTooltip (optional) + +- **Description**: Optional message to display on the Role Management screen when configuring permissions for this feature. +- **Type**: string +- **Example**: `"Configure privileges for this feature."` + +#### reserved (private) + +- **Description**: Private information about reserved privileges for this feature. +- **Type**: Object +- **Example**: + ```js + reserved: { + description: "Reserved privileges for advanced users.", + privileges: [ + // ... reserved privilege definitions + ] + } + ``` + + +### FeatureKibanaPrivileges Interface + +#### excludeFromBasePrivileges (optional) + +- **Description**: Whether or not this specific privilege should be excluded from the base privileges. +- **Type**: boolean +- **Example**: `true` + +#### requireAllSpaces (optional) + +- **Description**: Whether or not this privilege should only be granted to `All Spaces *`. Should be used for features that do not support Spaces. Defaults to `false`. +- **Type**: boolean +- **Example**: `false` + +#### disabled (optional) + +- **Description**: Whether or not this privilege should be hidden in the roles UI and disallowed on the API. Defaults to `false`. +- **Type**: boolean +- **Example**: `true` + +#### management (optional) + +- **Description**: If this feature includes management sections, you can specify them here to control visibility of those pages based on user privileges. +- **Type**: Object +- **Example**: + ```js + management: { + kibana: ["settings"], + myApp: ["config"] + } + ``` + +#### catalogue (optional) + +- **Description**: If this feature includes a catalogue entry, you can specify them here to control visibility based on user permissions. +- **Type**: string[] +- **Example**: `["cat1", "cat2"]` + +#### api (optional) + +- **Description**: If your feature includes server-side APIs, you can tag those routes to secure access based on user permissions. +- **Type**: string[] +- **Example**: `["my_feature-admin", "my_feature-user"]` + +#### app (optional) + +- **Description**: If your feature exposes a client-side application, you can control access to them here. +- **Type**: string[] +- **Example**: `["my-app", "kibana"]` + +#### alerting (optional) + +- **Description**: If your feature requires access to specific Alert Types, then specify your access needs here. +- **Type**: Object +- **Example**: + ```js + alerting: { + rule: { + all: ["my-alert-type-within-my-feature"], + read: ["my-alert-type"] + }, + alert: { + all: ["my-alert-type-within-my-feature"], + read: ["my-alert-type"] + } + } + ``` + +#### cases (optional) + +- **Description**: If your feature requires access to specific owners of cases, specify your access needs here. +- **Type**: Object +- **Example**: + ```js + cases: { + all: ["securitySolution"], + push: ["securitySolution"], + create: ["securitySolution"], + read: ["securitySolution"], + update: ["securitySolution"], + delete: ["securitySolution"] + } + ``` + +#### savedObject + +- **Description**: If your feature requires access to specific saved objects, then specify your access needs here. +- **Type**: Object +- **Example**: + ```js + savedObject: { + all: ["my-saved-object-type"], + read: ["config"] + } + ``` + +#### ui + +- **Description**: A list of UI Capabilities that should be granted to users with this privilege. These capabilities will automatically be namespaces within your feature id. +- **Type**: string[] +- **Example**: `["show", "save"]` diff --git a/examples/feature_control_examples/common/index.ts b/examples/feature_control_examples/common/index.ts new file mode 100644 index 0000000000000..aeb35cb1a4a96 --- /dev/null +++ b/examples/feature_control_examples/common/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 const FEATURE_PRIVILEGES_PLUGIN_ID = 'featurePrivilegesPluginExample'; diff --git a/examples/feature_control_examples/kibana.jsonc b/examples/feature_control_examples/kibana.jsonc new file mode 100644 index 0000000000000..61d3313585d40 --- /dev/null +++ b/examples/feature_control_examples/kibana.jsonc @@ -0,0 +1,13 @@ +{ + "type": "plugin", + "id": "@kbn/feature-controls-examples-plugin", + "owner": "@elastic/kibana-security", + "description": "Demo of how to implement feature controls", + "plugin": { + "id": "featureControlsExamples", + "server": true, + "browser": true, + "requiredBundles": ["kibanaReact"], + "requiredPlugins": ["developerExamples", "security", "spaces", "features"] + } +} diff --git a/examples/feature_control_examples/public/app.tsx b/examples/feature_control_examples/public/app.tsx new file mode 100644 index 0000000000000..835bed27780bd --- /dev/null +++ b/examples/feature_control_examples/public/app.tsx @@ -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 { EuiButton, EuiHealth, EuiPageTemplate, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import type { CoreStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React, { useEffect, useState } from 'react'; +import { FEATURE_PRIVILEGES_PLUGIN_ID } from '../common'; + +export const MyPluginComponent: React.FC = () => { + const [time, setTime] = useState(''); + const kibana = useKibana(); + + const fetchData = async () => { + const response = await fetch('/internal/my_plugin/read'); + const data = await response.json(); + + // console.log(data2); + setTime(data.time); + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + + + +

Feature Privileges Example

+
+
+ + +

Server Time: {time}

+
+ Refresh (Super user only) +
+ + +

Your privileges

+
+ + {Object.entries( + kibana.services.application!.capabilities[FEATURE_PRIVILEGES_PLUGIN_ID] + ).map(([capability, value]) => { + return value === true ? ( +
+ {capability} + +
+ ) : null; + })} +
+
+ ); +}; diff --git a/examples/feature_control_examples/public/index.tsx b/examples/feature_control_examples/public/index.tsx new file mode 100644 index 0000000000000..9310b9339da0a --- /dev/null +++ b/examples/feature_control_examples/public/index.tsx @@ -0,0 +1,59 @@ +/* + * 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 ReactDOM from 'react-dom'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; +import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; +import type { FeaturesPluginSetup } from '@kbn/features-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { MyPluginComponent } from './app'; + +interface SetupDeps { + developerExamples: DeveloperExamplesSetup; + security: SecurityPluginSetup; + features: FeaturesPluginSetup; +} + +interface StartDeps { + security: SecurityPluginStart; +} + +export class FeatureControlsPluginExample implements Plugin { + public setup(coreSetup: CoreSetup, deps: SetupDeps) { + coreSetup.application.register({ + id: 'featureControlsExamples', + title: 'FeatureControlExamples', + async mount({ element }: AppMountParameters) { + const [coreStart] = await coreSetup.getStartServices(); + ReactDOM.render( + + + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }, + }); + deps.developerExamples.register({ + appId: 'featureControlsExamples', + title: 'Feature Control Examples', + description: 'Demo of how to implement Feature Controls', + }); + } + + public start(core: CoreStart, deps: StartDeps) { + return {}; + } + + public stop() {} +} +export const plugin = () => new FeatureControlsPluginExample(); diff --git a/examples/feature_control_examples/server/index.ts b/examples/feature_control_examples/server/index.ts new file mode 100644 index 0000000000000..0db874ff2047e --- /dev/null +++ b/examples/feature_control_examples/server/index.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. + */ +import { PluginInitializer } from '@kbn/core/server'; +import { FeatureControlsPluginExample } from './plugin'; + +export const plugin: PluginInitializer = () => new FeatureControlsPluginExample(); diff --git a/examples/feature_control_examples/server/plugin.ts b/examples/feature_control_examples/server/plugin.ts new file mode 100644 index 0000000000000..95be91fbbde73 --- /dev/null +++ b/examples/feature_control_examples/server/plugin.ts @@ -0,0 +1,90 @@ +/* + * 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, DEFAULT_APP_CATEGORIES, Plugin } from '@kbn/core/server'; +import { + PluginSetupContract as FeaturesPluginSetup, + // PluginStartContract as FeaturesPluginStart, +} from '@kbn/features-plugin/server'; +import { FEATURE_PRIVILEGES_PLUGIN_ID } from '../common'; + +export interface FeatureControlExampleDeps { + features: FeaturesPluginSetup; +} + +export class FeatureControlsPluginExample + implements Plugin +{ + public setup(core: CoreSetup, { features }: FeatureControlExampleDeps) { + features.registerKibanaFeature({ + id: FEATURE_PRIVILEGES_PLUGIN_ID, + name: 'Feature Plugin Examples', + category: DEFAULT_APP_CATEGORIES.management, + app: ['FeaturePluginExample'], + privileges: { + all: { + app: ['FeaturePluginExample'], + savedObject: { + all: [], + read: [], + }, + api: ['my_closed_example_api'], + ui: ['view', 'create', 'edit', 'delete', 'assign'], + }, + read: { + app: ['FeaturePluginExample'], + savedObject: { + all: [], + read: ['tag'], + }, + api: [], + ui: ['view'], + }, + }, + }); + + const router = core.http.createRouter(); + router.get( + { + path: '/internal/my_plugin/read', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); + router.get( + { + path: '/internal/my_plugin/sensitive_action', + validate: false, + options: { + tags: ['access:my_closed_example_api'], + }, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); + } + + start() { + return {}; + } + + stop() { + return {}; + } +} diff --git a/examples/feature_control_examples/tsconfig.json b/examples/feature_control_examples/tsconfig.json new file mode 100644 index 0000000000000..e59fb8dac5614 --- /dev/null +++ b/examples/feature_control_examples/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": ["target/**/*"], + "kbn_references": [ + "@kbn/core", + "@kbn/security-plugin", + "@kbn/developer-examples-plugin", + "@kbn/shared-ux-page-kibana-template", + "@kbn/features-plugin", + "@kbn/kibana-react-plugin" + ] +} diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 87d63b80f30db..0c70e54167122 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -81,6 +81,10 @@ { "id": "kibDevDocsSecurityIntro" }, + { + "id": "kibDevFeaturePrivileges", + "label": "Feature Privileges" + }, { "id": "kibDevDocsSavedObjectsIntro", "label": "Saved objects" diff --git a/package.json b/package.json index e908c931fce58..c29325ea42092 100644 --- a/package.json +++ b/package.json @@ -425,6 +425,7 @@ "@kbn/expression-xy-plugin": "link:src/plugins/chart_expressions/expression_xy", "@kbn/expressions-explorer-plugin": "link:examples/expressions_explorer", "@kbn/expressions-plugin": "link:src/plugins/expressions", + "@kbn/feature-controls-examples-plugin": "link:examples/feature_control_examples", "@kbn/feature-usage-test-plugin": "link:x-pack/test/plugin_api_integration/plugins/feature_usage_test", "@kbn/features-plugin": "link:x-pack/plugins/features", "@kbn/fec-alerts-test-plugin": "link:x-pack/test/functional_execution_context/plugins/alerts", diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts index 4a5a147ffcbde..f6348fe2c11e2 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/autocomplete_definitions/dynamic_commands.ts @@ -45,9 +45,15 @@ export const buildNoPoliciesAvailableDefinition = (): AutocompleteCommandDefinit insertText: '', kind: 26, detail: i18n.translate('monaco.esql.autocomplete.noPoliciesLabelsFound', { - defaultMessage: 'No policies found', + defaultMessage: 'Click to create', }), sortText: 'D', + command: { + id: 'esql.policies.create', + title: i18n.translate('monaco.esql.autocomplete.createNewPolicy', { + defaultMessage: 'Click to create', + }), + }, }, ]; diff --git a/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts b/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts index 0b64f0871b27a..fc22bae7bbdb9 100644 --- a/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts +++ b/packages/kbn-monaco/src/esql/lib/autocomplete/types.ts @@ -32,5 +32,5 @@ export interface UserDefinedVariables { /** @internal **/ export type AutocompleteCommandDefinition = Pick< monaco.languages.CompletionItem, - 'label' | 'insertText' | 'kind' | 'detail' | 'documentation' | 'sortText' + 'label' | 'insertText' | 'kind' | 'detail' | 'documentation' | 'sortText' | 'command' >; diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index b1f1708262743..b84e459fd222d 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -131,7 +131,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const language = getAggregateQueryMode(query); const queryString: string = query[language] ?? ''; const kibana = useKibana(); - const { dataViews, expressions, indexManagementApiService } = kibana.services; + const { dataViews, expressions, indexManagementApiService, application } = kibana.services; const [code, setCode] = useState(queryString ?? ''); const [codeOneLiner, setCodeOneLiner] = useState(''); const [editorHeight, setEditorHeight] = useState( @@ -148,6 +148,16 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ useState(); const policiesRef = useRef([]); + + // Registers a command to redirect users to the index management page + // to create a new policy. The command is called by the buildNoPoliciesAvailableDefinition + monaco.editor.registerCommand('esql.policies.create', (...args) => { + application?.navigateToApp('management', { + path: 'data/index_management/enrich_policies/create', + openInNewTab: true, + }); + }); + const styles = textBasedLanguagedEditorStyles( euiTheme, isCompactFocused, diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts index ba1107dff6f8e..bea50cf32ce35 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/batch_size_bytes.test.ts @@ -55,8 +55,7 @@ async function fetchDocuments(esClient: ElasticsearchClient, index: string) { const assertMigratedDocuments = (arr: any[], target: any[]) => target.every((v) => arr.includes(v)); -// FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/160295 -describe.skip('migration v2', () => { +describe('migration v2', () => { let esServer: TestElasticsearchUtils; let root: Root; let startES: () => Promise; @@ -119,7 +118,7 @@ describe.skip('migration v2', () => { await root.preboot(); await root.setup(); await expect(root.start()).rejects.toMatchInlineSnapshot( - `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715318 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` + `[Error: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715319 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.]` ); await retryAsync( @@ -132,7 +131,7 @@ describe.skip('migration v2', () => { expect( records.find((rec) => rec.message.startsWith( - `Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715318 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` + `Reason: Unable to complete saved object migrations for the [.kibana] index: The document with _id "canvas-workpad-template:workpad-template-061d7868-2b4e-4dc8-8bf7-3772b52926e5" is 1715319 bytes which exceeds the configured maximum batch size of 1015275 bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.` ) ) ).toBeDefined(); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts index 9df98deea3b89..bb509799c052d 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts @@ -756,8 +756,7 @@ export const runActionTestSuite = ({ // Reindex doesn't return any errors on it's own, so we have to test // together with waitForReindexTask - // Failing: See https://github.com/elastic/kibana/issues/166190 - describe.skip('reindex & waitForReindexTask', () => { + describe('reindex & waitForReindexTask', () => { it('resolves right when reindex succeeds without reindex script', async () => { const res = (await reindex({ client, @@ -1120,15 +1119,17 @@ export const runActionTestSuite = ({ `); }); it('resolves left wait_for_task_completion_timeout when the task does not finish within the timeout', async () => { - await waitForIndexStatus({ + const readyTaskRes = await waitForIndexStatus({ client, - index: '.kibana_1', + index: 'existing_index_with_docs', status: 'yellow', })(); + expect(Either.isRight(readyTaskRes)).toBe(true); + const res = (await reindex({ client, - sourceIndex: '.kibana_1', + sourceIndex: 'existing_index_with_docs', targetIndex: 'reindex_target', reindexScript: Option.none, requireAlias: false, @@ -1467,7 +1468,7 @@ export const runActionTestSuite = ({ it('resolves left wait_for_task_completion_timeout when the task does not complete within the timeout', async () => { const res = (await pickupUpdatedMappings( client, - '.kibana_1', + 'existing_index_with_docs', 1000 )()) as Either.Right; diff --git a/src/plugins/console/public/lib/autocomplete/autocomplete.ts b/src/plugins/console/public/lib/autocomplete/autocomplete.ts index d6bb3879669ae..79adee3bd42b7 100644 --- a/src/plugins/console/public/lib/autocomplete/autocomplete.ts +++ b/src/plugins/console/public/lib/autocomplete/autocomplete.ts @@ -999,7 +999,7 @@ export default function ({ // will simulate autocomplete on 'GET /a/b/' with a filter by index return { tokenPath: context.urlTokenPath?.slice(0, -1), - predicate: (term: any) => term.meta === 'index', + predicate: (term: ReturnType[0]) => term.meta === 'index', }; } else { // will do nothing special diff --git a/src/plugins/home/public/application/components/tutorial/number_parameter.js b/src/plugins/home/public/application/components/tutorial/number_parameter.js index 41ad9767ab003..b2ac171393929 100644 --- a/src/plugins/home/public/application/components/tutorial/number_parameter.js +++ b/src/plugins/home/public/application/components/tutorial/number_parameter.js @@ -8,6 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; export function NumberParameter({ id, label, value, setParameter }) { const handleChange = (evt) => { @@ -15,20 +16,9 @@ export function NumberParameter({ id, label, value, setParameter }) { }; return ( -
- -
- -
-
+ + + ); } diff --git a/src/plugins/home/public/application/components/tutorial/string_parameter.js b/src/plugins/home/public/application/components/tutorial/string_parameter.js index 459a0ab8c1a53..4c8ad157f10fe 100644 --- a/src/plugins/home/public/application/components/tutorial/string_parameter.js +++ b/src/plugins/home/public/application/components/tutorial/string_parameter.js @@ -8,6 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { EuiFormRow, EuiFieldText } from '@elastic/eui'; export function StringParameter({ id, label, value, setParameter }) { const handleChange = (evt) => { @@ -15,12 +16,9 @@ export function StringParameter({ id, label, value, setParameter }) { }; return ( -
- -
- -
-
+ + + ); } diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts index 7f9b5e337cbb3..02a2ec4edb220 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs_tsdb/saved_objects.ts @@ -16,14 +16,15 @@ export const getSavedObjects = (): SavedObject[] => [ type: 'visualization', updated_at: '2021-10-28T15:07:36.622Z', version: '1', - coreMigrationVersion: '8.0.0', - migrationVersion: { visualization: '8.0.0' }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.0.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsTsdbSpec.visitorsMapTitle', { defaultMessage: '[Logs TSDB] Visitors Map', }), visState: - '{"title":"[Logs TSDB] Visitors Map","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 30, longitude: -120, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_logstsdb\\n %context%: true\\n %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n gridSplit: {\\n geotile_grid: {field: \\"geo.coordinates\\", precision: 5, size: 10000}\\n aggs: {\\n gridCentroid: {\\n geo_centroid: {\\n field: \\"geo.coordinates\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.gridSplit.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n gridCentroid.location.lon\\n gridCentroid.location.lat\\n ]\\n }\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: gridSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n 50\\n 1000\\n ]\\n }\\n {\\n name: bubbleColor\\n type: linear\\n domain: {\\n data: table\\n field: doc_count\\n }\\n range: [\\"rgb(249, 234, 197)\\",\\"rgb(243, 200, 154)\\",\\"rgb(235, 166, 114)\\", \\"rgb(231, 102, 76)\\"]\\n }\\n ]\\n marks: [\\n {\\n name: gridMarker\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n fill: {\\n scale: bubbleColor\\n field: doc_count\\n }\\n size: {scale: \\"gridSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n tooltip: {\\n signal: \\"{flights: datum.doc_count}\\"\\n }\\n }\\n }\\n }\\n ]\\n}"}}', + '{"title":"[Logs] Visitors Map","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega/v5.json\\n config: {\\n kibana: {type: \\"map\\", latitude: 30, longitude: -120, zoom: 3}\\n }\\n data: [\\n {\\n name: table\\n url: {\\n index: kibana_sample_data_logs\\n %context%: true\\n %timefield%: timestamp\\n body: {\\n size: 0\\n aggs: {\\n gridSplit: {\\n geotile_grid: {field: \\"geo.coordinates\\", precision: 5, size: 10000}\\n aggs: {\\n gridCentroid: {\\n geo_centroid: {\\n field: \\"geo.coordinates\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n format: {property: \\"aggregations.gridSplit.buckets\\"}\\n transform: [\\n {\\n type: geopoint\\n projection: projection\\n fields: [\\n gridCentroid.location.lon\\n gridCentroid.location.lat\\n ]\\n }\\n ]\\n }\\n ]\\n scales: [\\n {\\n name: gridSize\\n type: linear\\n domain: {data: \\"table\\", field: \\"doc_count\\"}\\n range: [\\n 50\\n 1000\\n ]\\n }\\n {\\n name: bubbleColor\\n type: linear\\n domain: {\\n data: table\\n field: doc_count\\n }\\n range: [\\"rgb(249, 234, 197)\\",\\"rgb(243, 200, 154)\\",\\"rgb(235, 166, 114)\\", \\"rgb(231, 102, 76)\\"]\\n }\\n ]\\n marks: [\\n {\\n name: gridMarker\\n type: symbol\\n from: {data: \\"table\\"}\\n encode: {\\n update: {\\n fill: {\\n scale: bubbleColor\\n field: doc_count\\n }\\n size: {scale: \\"gridSize\\", field: \\"doc_count\\"}\\n xc: {signal: \\"datum.x\\"}\\n yc: {signal: \\"datum.y\\"}\\n tooltip: {\\n signal: \\"{flights: datum.doc_count}\\"\\n }\\n }\\n }\\n }\\n ]\\n}"}}', uiStateJSON: '{}', description: '', version: 1, @@ -45,9 +46,9 @@ export const getSavedObjects = (): SavedObject[] => [ type: 'visualization', updated_at: '2021-07-21T21:33:42.541Z', version: '1', - migrationVersion: { - visualization: '7.14.0', - }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '7.14.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsTsdbSpec.heatmapTitle', { defaultMessage: '[Logs TSDB] Unique Destination Heatmap', @@ -59,81 +60,21 @@ export const getSavedObjects = (): SavedObject[] => [ uiStateJSON: '{}', version: 1, visState: - '{"title":"[Logs TSDB] Unique Destination Heatmap","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logstsdb\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.dest\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n },\\n {\\n filter: \\"datum.buckets.unique.value > 0\\"\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: {\\n expr: \\"{\\\\\\"Unique Visitors\\\\\\": datum.buckets.unique.value,\\\\\\"geo.src\\\\\\": datum.key,\\\\\\"Hour\\\\\\": datum.buckets.key}\\"\\n }\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: nominal\\n scale: {\\n domain: {\\n expr: \\"sequence(0, 24)\\"\\n }\\n }\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: blues\\n }\\n }\\n }\\n}\\n"}}', + '{"title":"[Logs] Unique Destination Heatmap","type":"vega","aggs":[],"params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.dest\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n },\\n {\\n filter: \\"datum.buckets.unique.value > 0\\"\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: {\\n expr: \\"{\\\\\\"Unique Visitors\\\\\\": datum.buckets.unique.value,\\\\\\"geo.src\\\\\\": datum.key,\\\\\\"Hour\\\\\\": datum.buckets.key}\\"\\n }\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: nominal\\n scale: {\\n domain: {\\n expr: \\"sequence(0, 24)\\"\\n }\\n }\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: blues\\n }\\n }\\n }\\n}\\n"}}', }, references: [], }, { - id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef8f5b', - type: 'visualization', - updated_at: '2021-07-21T18:52:13.586Z', - version: '2', - migrationVersion: { - visualization: '7.14.0', - }, - attributes: { - title: i18n.translate('home.sampleData.logsTsdbSpec.hostVisitsBytesTableTitle', { - defaultMessage: '[Logs TSDB] Host, Visits and Bytes Table', - }), - visState: - '{"title":"[Logs TSDB] Host, Visits and Bytes Table","type":"metrics","aggs":[],"params":{"time_range_mode":"last_value","id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"table","series":[{"id":"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum","field":"bytes"},{"sigma":"","id":"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum_bucket","field":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Total)","split_color_mode":"gradient"},{"id":"b7672c30-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"b7672c31-a6df-11e8-8b18-1da1dfc50975","type":"sum","field":"bytes"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Last Hour)","split_color_mode":"gradient","trend_arrows":1},{"id":"f2c20700-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"f2c20701-a6df-11e8-8b18-1da1dfc50975","type":"cardinality","field":"clientip"},{"sigma":"","id":"f46333e0-a6df-11e8-8b18-1da1dfc50975","type":"sum_bucket","field":"f2c20701-a6df-11e8-8b18-1da1dfc50975"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Total)","color_rules":[{"value":1000,"id":"2e963080-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":1000,"id":"3d4fb880-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":1500,"id":"435f8a20-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":0,"split_color_mode":"gradient"},{"id":"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Last Hour)","color_rules":[{"value":10,"id":"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":10,"id":"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":25,"id":"77578670-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1,"split_color_mode":"gradient"}],"time_field":"timestamp","use_kibana_indexes":true,"interval":"1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"bar_color_rules":[{"id":"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387"}],"pivot_id":"extension.keyword","pivot_label":"Type","drilldown_url":"","axis_scale":"normal","hide_last_value_indicator":false,"tooltip_mode":"show_all","drop_last_bucket":0,"isModelInvalid":false,"index_pattern_ref_name":"metrics_0_index_pattern"}}', - uiStateJSON: '{}', - description: '', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', - }, - }, - references: [ - { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - type: 'index-pattern', - name: 'metrics_0_index_pattern', - }, - ], - }, - { - id: '69a34b00-9ee8-11e7-8711-e7a007dcff99', - type: 'visualization', - updated_at: '2021-10-28T14:38:21.435Z', - version: '2', - coreMigrationVersion: '8.0.0', - migrationVersion: { visualization: '8.0.0' }, - attributes: { - title: i18n.translate('home.sampleData.logsTsdbSpec.goalsTitle', { - defaultMessage: '[Logs TSDB] Goals', - }), - visState: - '{"title":"[Logs TSDB] Goals","type":"gauge","params":{"type":"gauge","addTooltip":true,"addLegend":false,"gauge":{"extendRange":true,"percentageMode":false,"gaugeType":"Arc","gaugeStyle":"Full","backStyle":"Full","orientation":"vertical","colorSchema":"Green to Red","gaugeColorMode":"Labels","colorsRange":[{"from":0,"to":500},{"from":500,"to":1000},{"from":1000,"to":1500}],"invertColors":true,"labels":{"show":false,"color":"black"},"scale":{"show":true,"labels":false,"color":"#333"},"type":"meter","style":{"bgWidth":0.9,"width":0.9,"mask":false,"bgMask":false,"maskBars":50,"bgFill":"#eee","bgColor":false,"subText":"visitors","fontSize":60,"labelColor":true},"alignment":"horizontal"},"isDisplayWarning":false},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}}]}', - uiStateJSON: - '{"vis":{"defaultColors":{"0 - 500":"rgb(165,0,38)","500 - 1000":"rgb(255,255,190)","1000 - 1500":"rgb(0,104,55)"},"colors":{"75 - 100":"#629E51","50 - 75":"#EAB839","0 - 50":"#E24D42","0 - 100":"#E24D42","200 - 300":"#7EB26D","500 - 1000":"#E5AC0E","0 - 500":"#E24D42","1000 - 1500":"#7EB26D"},"legendOpen":true}}', - description: '', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: - '{"filter":[],"query":{"query":"","language":"kuery"},"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}', - }, - }, - references: [ - { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - }, - ], - }, - { - id: '7cbd2350-2223-11e8-b802-5bcf64c2dfb4', + id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', type: 'visualization', updated_at: '2018-08-29T13:22:17.617Z', version: '1', - migrationVersion: {}, attributes: { title: i18n.translate('home.sampleData.logsTsdbSpec.sourceAndDestinationSankeyChartTitle', { defaultMessage: '[Logs TSDB] Machine OS and Destination Sankey Chart', }), visState: - '{"title":"[Logs TSDB] Machine OS and Destination Sankey Chart","type":"vega","params":{"spec":"{ \\n $schema: https://vega.github.io/schema/vega/v5.json\\n data: [\\n\\t{\\n \\t// query ES based on the currently selected time range and filter string\\n \\tname: rawData\\n \\turl: {\\n \\t%context%: true\\n \\t%timefield%: timestamp\\n \\tindex: kibana_sample_data_logstsdb\\n \\tbody: {\\n \\tsize: 0\\n \\taggs: {\\n \\ttable: {\\n \\tcomposite: {\\n \\tsize: 10000\\n \\tsources: [\\n \\t{\\n \\tstk1: {\\n \\tterms: {field: \\"machine.os.keyword\\"}\\n \\t}\\n \\t}\\n \\t{\\n \\tstk2: {\\n \\tterms: {field: \\"geo.dest\\"}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t// From the result, take just the data we are interested in\\n \\tformat: {property: \\"aggregations.table.buckets\\"}\\n \\t// Convert key.stk1 -> stk1 for simpler access below\\n \\ttransform: [\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk1\\", as: \\"stk1\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk2\\", as: \\"stk2\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.doc_count\\", as: \\"size\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: nodes\\n \\tsource: rawData\\n \\ttransform: [\\n \\t// when a country is selected, filter out unrelated data\\n \\t{\\n \\ttype: filter\\n \\texpr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2\\n \\t}\\n \\t// Set new key for later lookups - identifies each node\\n \\t{type: \\"formula\\", expr: \\"datum.stk1+datum.stk2\\", as: \\"key\\"}\\n \\t// instead of each table row, create two new rows,\\n \\t// one for the source (stack=stk1) and one for destination node (stack=stk2).\\n \\t// The country code stored in stk1 and stk2 fields is placed into grpId field.\\n \\t{\\n \\ttype: fold\\n \\tfields: [\\"stk1\\", \\"stk2\\"]\\n \\tas: [\\"stack\\", \\"grpId\\"]\\n \\t}\\n \\t// Create a sortkey, different for stk1 and stk2 stacks.\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.stack == \'stk1\' ? datum.stk1+datum.stk2 : datum.stk2+datum.stk1\\n \\tas: sortField\\n \\t}\\n \\t// Calculate y0 and y1 positions for stacking nodes one on top of the other,\\n \\t// independently for each stack, and ensuring they are in the proper order,\\n \\t// alphabetical from the top (reversed on the y axis)\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"sortField\\", order: \\"descending\\"}\\n \\tfield: size\\n \\t}\\n \\t// calculate vertical center point for each node, used to draw edges\\n \\t{type: \\"formula\\", expr: \\"(datum.y0+datum.y1)/2\\", as: \\"yc\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: groups\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// combine all nodes into country groups, summing up the doc counts\\n \\t{\\n \\ttype: aggregate\\n \\tgroupby: [\\"stack\\", \\"grpId\\"]\\n \\tfields: [\\"size\\"]\\n \\tops: [\\"sum\\"]\\n \\tas: [\\"total\\"]\\n \\t}\\n \\t// re-calculate the stacking y0,y1 values\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"grpId\\", order: \\"descending\\"}\\n \\tfield: total\\n \\t}\\n \\t// project y0 and y1 values to screen coordinates\\n \\t// doing it once here instead of doing it several times in marks\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y0)\\", as: \\"scaledY0\\"}\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y1)\\", as: \\"scaledY1\\"}\\n \\t// boolean flag if the label should be on the right of the stack\\n \\t{type: \\"formula\\", expr: \\"datum.stack == \'stk1\'\\", as: \\"rightLabel\\"}\\n \\t// Calculate traffic percentage for this country using \\"y\\" scale\\n \\t// domain upper bound, which represents the total traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.total/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n\\t{\\n \\t// This is a temp lookup table with all the \'stk2\' stack nodes\\n \\tname: destinationNodes\\n \\tsource: nodes\\n \\ttransform: [\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk2\'\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: edges\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// we only want nodes from the left stack\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk1\'\\"}\\n \\t// find corresponding node from the right stack, keep it as \\"target\\"\\n \\t{\\n \\ttype: lookup\\n \\tfrom: destinationNodes\\n \\tkey: key\\n \\tfields: [\\"key\\"]\\n \\tas: [\\"target\\"]\\n \\t}\\n \\t// calculate SVG link path between stk1 and stk2 stacks for the node pair\\n \\t{\\n \\ttype: linkpath\\n \\torient: horizontal\\n \\tshape: diagonal\\n \\tsourceY: {expr: \\"scale(\'y\', datum.yc)\\"}\\n \\tsourceX: {expr: \\"scale(\'x\', \'stk1\') + bandwidth(\'x\')\\"}\\n \\ttargetY: {expr: \\"scale(\'y\', datum.target.yc)\\"}\\n \\ttargetX: {expr: \\"scale(\'x\', \'stk2\')\\"}\\n \\t}\\n \\t// A little trick to calculate the thickness of the line.\\n \\t// The value needs to be the same as the hight of the node, but scaling\\n \\t// size to screen\'s height gives inversed value because screen\'s Y\\n \\t// coordinate goes from the top to the bottom, whereas the graph\'s Y=0\\n \\t// is at the bottom. So subtracting scaled doc count from screen height\\n \\t// (which is the \\"lower\\" bound of the \\"y\\" scale) gives us the right value\\n \\t{\\n \\ttype: formula\\n \\texpr: range(\'y\')[0]-scale(\'y\', datum.size)\\n \\tas: strokeWidth\\n \\t}\\n \\t// Tooltip needs individual link\'s percentage of all traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.size/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n ]\\n scales: [\\n\\t{\\n \\t// calculates horizontal stack positioning\\n \\tname: x\\n \\ttype: band\\n \\trange: width\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n \\tpaddingOuter: 0.05\\n \\tpaddingInner: 0.95\\n\\t}\\n\\t{\\n \\t// this scale goes up as high as the highest y1 value of all nodes\\n \\tname: y\\n \\ttype: linear\\n \\trange: height\\n \\tdomain: {data: \\"nodes\\", field: \\"y1\\"}\\n\\t}\\n\\t{\\n \\t// use rawData to ensure the colors stay the same when clicking.\\n \\tname: color\\n \\ttype: ordinal\\n \\trange: category\\n \\tdomain: {data: \\"rawData\\", field: \\"stk1\\"}\\n\\t}\\n\\t{\\n \\t// this scale is used to map internal ids (stk1, stk2) to stack names\\n \\tname: stackNames\\n \\ttype: ordinal\\n \\trange: [\\"Source\\", \\"Destination\\"]\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n\\t}\\n ]\\n axes: [\\n\\t{\\n \\t// x axis should use custom label formatting to print proper stack names\\n \\torient: bottom\\n \\tscale: x\\n \\tencode: {\\n \\tlabels: {\\n \\tupdate: {\\n \\ttext: {scale: \\"stackNames\\", field: \\"value\\"}\\n \\t}\\n \\t}\\n \\t}\\n\\t}\\n\\t{orient: \\"left\\", scale: \\"y\\"}\\n ]\\n marks: [\\n\\t{\\n \\t// draw the connecting line between stacks\\n \\ttype: path\\n \\tname: edgeMark\\n \\tfrom: {data: \\"edges\\"}\\n \\t// this prevents some autosizing issues with large strokeWidth for paths\\n \\tclip: true\\n \\tencode: {\\n \\tupdate: {\\n \\t// By default use color of the left node, except when showing traffic\\n \\t// from just one country, in which case use destination color.\\n \\tstroke: [\\n \\t{\\n \\ttest: groupSelector && groupSelector.stack==\'stk1\'\\n \\tscale: color\\n \\tfield: stk2\\n \\t}\\n \\t{scale: \\"color\\", field: \\"stk1\\"}\\n \\t]\\n \\tstrokeWidth: {field: \\"strokeWidth\\"}\\n \\tpath: {field: \\"path\\"}\\n \\t// when showing all traffic, and hovering over a country,\\n \\t// highlight the traffic from that country.\\n \\tstrokeOpacity: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3\\n \\t}\\n \\t// Ensure that the hover-selected edges show on top\\n \\tzindex: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0\\n \\t}\\n \\t// format tooltip string\\n \\ttooltip: {\\n \\tsignal: datum.stk1 + \' → \' + datum.stk2 + \'\\t\' + format(datum.size, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\t// Simple mouseover highlighting of a single line\\n \\thover: {\\n \\tstrokeOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw stack groups (countries)\\n \\ttype: rect\\n \\tname: groupMark\\n \\tfrom: {data: \\"groups\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tfill: {scale: \\"color\\", field: \\"grpId\\"}\\n \\twidth: {scale: \\"x\\", band: 1}\\n \\t}\\n \\tupdate: {\\n \\tx: {scale: \\"x\\", field: \\"stack\\"}\\n \\ty: {field: \\"scaledY0\\"}\\n \\ty2: {field: \\"scaledY1\\"}\\n \\tfillOpacity: {value: 0.6}\\n \\ttooltip: {\\n \\tsignal: datum.grpId + \' \' + format(datum.total, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\thover: {\\n \\tfillOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw country code labels on the inner side of the stack\\n \\ttype: text\\n \\tfrom: {data: \\"groups\\"}\\n \\t// don\'t process events for the labels - otherwise line mouseover is unclean\\n \\tinteractive: false\\n \\tencode: {\\n \\tupdate: {\\n \\t// depending on which stack it is, position x with some padding\\n \\tx: {\\n \\tsignal: scale(\'x\', datum.stack) + (datum.rightLabel ? bandwidth(\'x\') + 8 : -8)\\n \\t}\\n \\t// middle of the group\\n \\tyc: {signal: \\"(datum.scaledY0 + datum.scaledY1)/2\\"}\\n \\talign: {signal: \\"datum.rightLabel ? \'left\' : \'right\'\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\t// only show text label if the group\'s height is large enough\\n \\ttext: {signal: \\"abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : \'\'\\"}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// Create a \\"show all\\" button. Shown only when a country is selected.\\n \\ttype: group\\n \\tdata: [\\n \\t// We need to make the button show only when groupSelector signal is true.\\n \\t// Each mark is drawn as many times as there are elements in the backing data.\\n \\t// Which means that if values list is empty, it will not be drawn.\\n \\t// Here I create a data source with one empty object, and filter that list\\n \\t// based on the signal value. This can only be done in a group.\\n \\t{\\n \\tname: dataForShowAll\\n \\tvalues: [{}]\\n \\ttransform: [{type: \\"filter\\", expr: \\"groupSelector\\"}]\\n \\t}\\n \\t]\\n \\t// Set button size and positioning\\n \\tencode: {\\n \\tenter: {\\n \\txc: {signal: \\"width/2\\"}\\n \\ty: {value: 30}\\n \\twidth: {value: 80}\\n \\theight: {value: 30}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\t// This group is shown as a button with rounded corners.\\n \\ttype: group\\n \\t// mark name allows signal capturing\\n \\tname: groupReset\\n \\t// Only shows button if dataForShowAll has values.\\n \\tfrom: {data: \\"dataForShowAll\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tcornerRadius: {value: 6}\\n \\tfill: {value: \\"#F5F7FA\\"}\\n \\tstroke: {value: \\"#c1c1c1\\"}\\n \\tstrokeWidth: {value: 2}\\n \\t// use parent group\'s size\\n \\theight: {\\n \\tfield: {group: \\"height\\"}\\n \\t}\\n \\twidth: {\\n \\tfield: {group: \\"width\\"}\\n \\t}\\n \\t}\\n \\tupdate: {\\n \\t// groups are transparent by default\\n \\topacity: {value: 1}\\n \\t}\\n \\thover: {\\n \\topacity: {value: 0.7}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\ttype: text\\n \\t// if true, it will prevent clicking on the button when over text.\\n \\tinteractive: false\\n \\tencode: {\\n \\tenter: {\\n \\t// center text in the paren group\\n \\txc: {\\n \\tfield: {group: \\"width\\"}\\n \\tmult: 0.5\\n \\t}\\n \\tyc: {\\n \\tfield: {group: \\"height\\"}\\n \\tmult: 0.5\\n \\toffset: 2\\n \\t}\\n \\talign: {value: \\"center\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\ttext: {value: \\"Show All\\"}\\n \\t}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t]\\n\\t}\\n ]\\n signals: [\\n\\t{\\n \\t// used to highlight traffic to/from the same country\\n \\tname: groupHover\\n \\tvalue: {}\\n \\ton: [\\n \\t{\\n \\tevents: @groupMark:mouseover\\n \\tupdate: \\"{stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{events: \\"mouseout\\", update: \\"{}\\"}\\n \\t]\\n\\t}\\n\\t// used to filter only the data related to the selected country\\n\\t{\\n \\tname: groupSelector\\n \\tvalue: false\\n \\ton: [\\n \\t{\\n \\t// Clicking groupMark sets this signal to the filter values\\n \\tevents: @groupMark:click!\\n \\tupdate: \\"{stack:datum.stack, stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{\\n \\t// Clicking \\"show all\\" button, or double-clicking anywhere resets it\\n \\tevents: [\\n \\t{type: \\"click\\", markname: \\"groupReset\\"}\\n \\t{type: \\"dblclick\\"}\\n \\t]\\n \\tupdate: \\"false\\"\\n \\t}\\n \\t]\\n\\t}\\n ]\\n}\\n"},"aggs":[]}', + '{"title":"[Logs] Machine OS and Destination Sankey Chart","type":"vega","params":{"spec":"{ \\n $schema: https://vega.github.io/schema/vega/v5.json\\n data: [\\n\\t{\\n \\t// query ES based on the currently selected time range and filter string\\n \\tname: rawData\\n \\turl: {\\n \\t%context%: true\\n \\t%timefield%: timestamp\\n \\tindex: kibana_sample_data_logs\\n \\tbody: {\\n \\tsize: 0\\n \\taggs: {\\n \\ttable: {\\n \\tcomposite: {\\n \\tsize: 10000\\n \\tsources: [\\n \\t{\\n \\tstk1: {\\n \\tterms: {field: \\"machine.os.keyword\\"}\\n \\t}\\n \\t}\\n \\t{\\n \\tstk2: {\\n \\tterms: {field: \\"geo.dest\\"}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t}\\n \\t// From the result, take just the data we are interested in\\n \\tformat: {property: \\"aggregations.table.buckets\\"}\\n \\t// Convert key.stk1 -> stk1 for simpler access below\\n \\ttransform: [\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk1\\", as: \\"stk1\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.key.stk2\\", as: \\"stk2\\"}\\n \\t{type: \\"formula\\", expr: \\"datum.doc_count\\", as: \\"size\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: nodes\\n \\tsource: rawData\\n \\ttransform: [\\n \\t// when a country is selected, filter out unrelated data\\n \\t{\\n \\ttype: filter\\n \\texpr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2\\n \\t}\\n \\t// Set new key for later lookups - identifies each node\\n \\t{type: \\"formula\\", expr: \\"datum.stk1+datum.stk2\\", as: \\"key\\"}\\n \\t// instead of each table row, create two new rows,\\n \\t// one for the source (stack=stk1) and one for destination node (stack=stk2).\\n \\t// The country code stored in stk1 and stk2 fields is placed into grpId field.\\n \\t{\\n \\ttype: fold\\n \\tfields: [\\"stk1\\", \\"stk2\\"]\\n \\tas: [\\"stack\\", \\"grpId\\"]\\n \\t}\\n \\t// Create a sortkey, different for stk1 and stk2 stacks.\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.stack == \'stk1\' ? datum.stk1+datum.stk2 : datum.stk2+datum.stk1\\n \\tas: sortField\\n \\t}\\n \\t// Calculate y0 and y1 positions for stacking nodes one on top of the other,\\n \\t// independently for each stack, and ensuring they are in the proper order,\\n \\t// alphabetical from the top (reversed on the y axis)\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"sortField\\", order: \\"descending\\"}\\n \\tfield: size\\n \\t}\\n \\t// calculate vertical center point for each node, used to draw edges\\n \\t{type: \\"formula\\", expr: \\"(datum.y0+datum.y1)/2\\", as: \\"yc\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: groups\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// combine all nodes into country groups, summing up the doc counts\\n \\t{\\n \\ttype: aggregate\\n \\tgroupby: [\\"stack\\", \\"grpId\\"]\\n \\tfields: [\\"size\\"]\\n \\tops: [\\"sum\\"]\\n \\tas: [\\"total\\"]\\n \\t}\\n \\t// re-calculate the stacking y0,y1 values\\n \\t{\\n \\ttype: stack\\n \\tgroupby: [\\"stack\\"]\\n \\tsort: {field: \\"grpId\\", order: \\"descending\\"}\\n \\tfield: total\\n \\t}\\n \\t// project y0 and y1 values to screen coordinates\\n \\t// doing it once here instead of doing it several times in marks\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y0)\\", as: \\"scaledY0\\"}\\n \\t{type: \\"formula\\", expr: \\"scale(\'y\', datum.y1)\\", as: \\"scaledY1\\"}\\n \\t// boolean flag if the label should be on the right of the stack\\n \\t{type: \\"formula\\", expr: \\"datum.stack == \'stk1\'\\", as: \\"rightLabel\\"}\\n \\t// Calculate traffic percentage for this country using \\"y\\" scale\\n \\t// domain upper bound, which represents the total traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.total/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n\\t{\\n \\t// This is a temp lookup table with all the \'stk2\' stack nodes\\n \\tname: destinationNodes\\n \\tsource: nodes\\n \\ttransform: [\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk2\'\\"}\\n \\t]\\n\\t}\\n\\t{\\n \\tname: edges\\n \\tsource: nodes\\n \\ttransform: [\\n \\t// we only want nodes from the left stack\\n \\t{type: \\"filter\\", expr: \\"datum.stack == \'stk1\'\\"}\\n \\t// find corresponding node from the right stack, keep it as \\"target\\"\\n \\t{\\n \\ttype: lookup\\n \\tfrom: destinationNodes\\n \\tkey: key\\n \\tfields: [\\"key\\"]\\n \\tas: [\\"target\\"]\\n \\t}\\n \\t// calculate SVG link path between stk1 and stk2 stacks for the node pair\\n \\t{\\n \\ttype: linkpath\\n \\torient: horizontal\\n \\tshape: diagonal\\n \\tsourceY: {expr: \\"scale(\'y\', datum.yc)\\"}\\n \\tsourceX: {expr: \\"scale(\'x\', \'stk1\') + bandwidth(\'x\')\\"}\\n \\ttargetY: {expr: \\"scale(\'y\', datum.target.yc)\\"}\\n \\ttargetX: {expr: \\"scale(\'x\', \'stk2\')\\"}\\n \\t}\\n \\t// A little trick to calculate the thickness of the line.\\n \\t// The value needs to be the same as the hight of the node, but scaling\\n \\t// size to screen\'s height gives inversed value because screen\'s Y\\n \\t// coordinate goes from the top to the bottom, whereas the graph\'s Y=0\\n \\t// is at the bottom. So subtracting scaled doc count from screen height\\n \\t// (which is the \\"lower\\" bound of the \\"y\\" scale) gives us the right value\\n \\t{\\n \\ttype: formula\\n \\texpr: range(\'y\')[0]-scale(\'y\', datum.size)\\n \\tas: strokeWidth\\n \\t}\\n \\t// Tooltip needs individual link\'s percentage of all traffic\\n \\t{\\n \\ttype: formula\\n \\texpr: datum.size/domain(\'y\')[1]\\n \\tas: percentage\\n \\t}\\n \\t]\\n\\t}\\n ]\\n scales: [\\n\\t{\\n \\t// calculates horizontal stack positioning\\n \\tname: x\\n \\ttype: band\\n \\trange: width\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n \\tpaddingOuter: 0.05\\n \\tpaddingInner: 0.95\\n\\t}\\n\\t{\\n \\t// this scale goes up as high as the highest y1 value of all nodes\\n \\tname: y\\n \\ttype: linear\\n \\trange: height\\n \\tdomain: {data: \\"nodes\\", field: \\"y1\\"}\\n\\t}\\n\\t{\\n \\t// use rawData to ensure the colors stay the same when clicking.\\n \\tname: color\\n \\ttype: ordinal\\n \\trange: category\\n \\tdomain: {data: \\"rawData\\", field: \\"stk1\\"}\\n\\t}\\n\\t{\\n \\t// this scale is used to map internal ids (stk1, stk2) to stack names\\n \\tname: stackNames\\n \\ttype: ordinal\\n \\trange: [\\"Source\\", \\"Destination\\"]\\n \\tdomain: [\\"stk1\\", \\"stk2\\"]\\n\\t}\\n ]\\n axes: [\\n\\t{\\n \\t// x axis should use custom label formatting to print proper stack names\\n \\torient: bottom\\n \\tscale: x\\n \\tencode: {\\n \\tlabels: {\\n \\tupdate: {\\n \\ttext: {scale: \\"stackNames\\", field: \\"value\\"}\\n \\t}\\n \\t}\\n \\t}\\n\\t}\\n\\t{orient: \\"left\\", scale: \\"y\\"}\\n ]\\n marks: [\\n\\t{\\n \\t// draw the connecting line between stacks\\n \\ttype: path\\n \\tname: edgeMark\\n \\tfrom: {data: \\"edges\\"}\\n \\t// this prevents some autosizing issues with large strokeWidth for paths\\n \\tclip: true\\n \\tencode: {\\n \\tupdate: {\\n \\t// By default use color of the left node, except when showing traffic\\n \\t// from just one country, in which case use destination color.\\n \\tstroke: [\\n \\t{\\n \\ttest: groupSelector && groupSelector.stack==\'stk1\'\\n \\tscale: color\\n \\tfield: stk2\\n \\t}\\n \\t{scale: \\"color\\", field: \\"stk1\\"}\\n \\t]\\n \\tstrokeWidth: {field: \\"strokeWidth\\"}\\n \\tpath: {field: \\"path\\"}\\n \\t// when showing all traffic, and hovering over a country,\\n \\t// highlight the traffic from that country.\\n \\tstrokeOpacity: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3\\n \\t}\\n \\t// Ensure that the hover-selected edges show on top\\n \\tzindex: {\\n \\tsignal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0\\n \\t}\\n \\t// format tooltip string\\n \\ttooltip: {\\n \\tsignal: datum.stk1 + \' → \' + datum.stk2 + \'\\t\' + format(datum.size, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\t// Simple mouseover highlighting of a single line\\n \\thover: {\\n \\tstrokeOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw stack groups (countries)\\n \\ttype: rect\\n \\tname: groupMark\\n \\tfrom: {data: \\"groups\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tfill: {scale: \\"color\\", field: \\"grpId\\"}\\n \\twidth: {scale: \\"x\\", band: 1}\\n \\t}\\n \\tupdate: {\\n \\tx: {scale: \\"x\\", field: \\"stack\\"}\\n \\ty: {field: \\"scaledY0\\"}\\n \\ty2: {field: \\"scaledY1\\"}\\n \\tfillOpacity: {value: 0.6}\\n \\ttooltip: {\\n \\tsignal: datum.grpId + \' \' + format(datum.total, \',.0f\') + \' (\' + format(datum.percentage, \'.1%\') + \')\'\\n \\t}\\n \\t}\\n \\thover: {\\n \\tfillOpacity: {value: 1}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// draw country code labels on the inner side of the stack\\n \\ttype: text\\n \\tfrom: {data: \\"groups\\"}\\n \\t// don\'t process events for the labels - otherwise line mouseover is unclean\\n \\tinteractive: false\\n \\tencode: {\\n \\tupdate: {\\n \\t// depending on which stack it is, position x with some padding\\n \\tx: {\\n \\tsignal: scale(\'x\', datum.stack) + (datum.rightLabel ? bandwidth(\'x\') + 8 : -8)\\n \\t}\\n \\t// middle of the group\\n \\tyc: {signal: \\"(datum.scaledY0 + datum.scaledY1)/2\\"}\\n \\talign: {signal: \\"datum.rightLabel ? \'left\' : \'right\'\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\t// only show text label if the group\'s height is large enough\\n \\ttext: {signal: \\"abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : \'\'\\"}\\n \\t}\\n \\t}\\n\\t}\\n\\t{\\n \\t// Create a \\"show all\\" button. Shown only when a country is selected.\\n \\ttype: group\\n \\tdata: [\\n \\t// We need to make the button show only when groupSelector signal is true.\\n \\t// Each mark is drawn as many times as there are elements in the backing data.\\n \\t// Which means that if values list is empty, it will not be drawn.\\n \\t// Here I create a data source with one empty object, and filter that list\\n \\t// based on the signal value. This can only be done in a group.\\n \\t{\\n \\tname: dataForShowAll\\n \\tvalues: [{}]\\n \\ttransform: [{type: \\"filter\\", expr: \\"groupSelector\\"}]\\n \\t}\\n \\t]\\n \\t// Set button size and positioning\\n \\tencode: {\\n \\tenter: {\\n \\txc: {signal: \\"width/2\\"}\\n \\ty: {value: 30}\\n \\twidth: {value: 80}\\n \\theight: {value: 30}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\t// This group is shown as a button with rounded corners.\\n \\ttype: group\\n \\t// mark name allows signal capturing\\n \\tname: groupReset\\n \\t// Only shows button if dataForShowAll has values.\\n \\tfrom: {data: \\"dataForShowAll\\"}\\n \\tencode: {\\n \\tenter: {\\n \\tcornerRadius: {value: 6}\\n \\tfill: {value: \\"#F5F7FA\\"}\\n \\tstroke: {value: \\"#c1c1c1\\"}\\n \\tstrokeWidth: {value: 2}\\n \\t// use parent group\'s size\\n \\theight: {\\n \\tfield: {group: \\"height\\"}\\n \\t}\\n \\twidth: {\\n \\tfield: {group: \\"width\\"}\\n \\t}\\n \\t}\\n \\tupdate: {\\n \\t// groups are transparent by default\\n \\topacity: {value: 1}\\n \\t}\\n \\thover: {\\n \\topacity: {value: 0.7}\\n \\t}\\n \\t}\\n \\tmarks: [\\n \\t{\\n \\ttype: text\\n \\t// if true, it will prevent clicking on the button when over text.\\n \\tinteractive: false\\n \\tencode: {\\n \\tenter: {\\n \\t// center text in the paren group\\n \\txc: {\\n \\tfield: {group: \\"width\\"}\\n \\tmult: 0.5\\n \\t}\\n \\tyc: {\\n \\tfield: {group: \\"height\\"}\\n \\tmult: 0.5\\n \\toffset: 2\\n \\t}\\n \\talign: {value: \\"center\\"}\\n \\tbaseline: {value: \\"middle\\"}\\n \\tfontWeight: {value: \\"bold\\"}\\n \\ttext: {value: \\"Show All\\"}\\n \\t}\\n \\t}\\n \\t}\\n \\t]\\n \\t}\\n \\t]\\n\\t}\\n ]\\n signals: [\\n\\t{\\n \\t// used to highlight traffic to/from the same country\\n \\tname: groupHover\\n \\tvalue: {}\\n \\ton: [\\n \\t{\\n \\tevents: @groupMark:mouseover\\n \\tupdate: \\"{stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{events: \\"mouseout\\", update: \\"{}\\"}\\n \\t]\\n\\t}\\n\\t// used to filter only the data related to the selected country\\n\\t{\\n \\tname: groupSelector\\n \\tvalue: false\\n \\ton: [\\n \\t{\\n \\t// Clicking groupMark sets this signal to the filter values\\n \\tevents: @groupMark:click!\\n \\tupdate: \\"{stack:datum.stack, stk1:datum.stack==\'stk1\' && datum.grpId, stk2:datum.stack==\'stk2\' && datum.grpId}\\"\\n \\t}\\n \\t{\\n \\t// Clicking \\"show all\\" button, or double-clicking anywhere resets it\\n \\tevents: [\\n \\t{type: \\"click\\", markname: \\"groupReset\\"}\\n \\t{type: \\"dblclick\\"}\\n \\t]\\n \\tupdate: \\"false\\"\\n \\t}\\n \\t]\\n\\t}\\n ]\\n}\\n"},"aggs":[]}', uiStateJSON: '{}', description: '', version: 1, @@ -144,47 +85,13 @@ export const getSavedObjects = (): SavedObject[] => [ references: [], }, { - id: '314c6f60-2224-11e8-b802-5bcf64c2dfb4', - type: 'visualization', - updated_at: '2021-07-21T18:52:13.586Z', - version: '2', - migrationVersion: { - visualization: '7.14.0', - }, - attributes: { - title: i18n.translate('home.sampleData.logsTsdbSpec.responseCodesOverTimeTitle', { - defaultMessage: '[Logs TSDB] Response Codes Over Time + Annotations', - }), - visState: - '{"title":"[Logs TSDB] Response Codes Over Time + Annotations","type":"metrics","aggs":[],"params":{"time_range_mode":"entire_time_range","id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(115,216,255,1)","split_mode":"filters","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":"0.5","stacked":"percent","terms_field":"response.keyword","terms_order_by":"61ca57f2-469d-11e7-af02-69e470af7417","label":"Response Code Count","split_color_mode":"gradient","split_filters":[{"filter":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx","color":"rgba(84,179,153,1)","id":"96b6ffe0-ea54-11eb-ad09-9f2ab44412fb"},{"filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx","color":"rgba(214,191,87,1)","id":"9e41b1b0-ea54-11eb-ad09-9f2ab44412fb"},{"filter":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx","color":"rgba(211,96,134,1)","id":"a6772270-ea54-11eb-ad09-9f2ab44412fb"}],"type":"timeseries"}],"time_field":"timestamp","use_kibana_indexes":true,"interval":">=4h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"geo.src, host","template":"Security Error from {{geo.src}} on {{host}}","query_string":{"query":"tags:error AND tags:security","language":"lucene"},"id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","color":"rgba(211,49,21,1)","time_field":"timestamp","icon":"fa-asterisk","ignore_global_filters":1,"ignore_panel_filters":1,"index_pattern_ref_name":"metrics_1_index_pattern"}],"legend_position":"bottom","axis_scale":"normal","drop_last_bucket":0,"tooltip_mode":"show_all","index_pattern_ref_name":"metrics_0_index_pattern"}}', - uiStateJSON: '{}', - description: '', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', - }, - }, - references: [ - { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: 'metrics_0_index_pattern', - type: 'index-pattern', - }, - { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: 'metrics_1_index_pattern', - type: 'index-pattern', - }, - ], - }, - { - id: '16b1d7d0-ea71-11eb-8b4b-f7b600de1f7d', + id: '16b1d7d0-ea71-11eb-8b4b-f7b600de0f7d', type: 'lens', updated_at: '2021-07-21T22:14:59.793Z', version: '1', - migrationVersion: { - lens: '7.14.0', - }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '7.14.0', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsTsdbSpec.bytesDistributionTitle', { defaultMessage: '[Logs TSDB] Bytes distribution', @@ -367,7 +274,6 @@ export const getSavedObjects = (): SavedObject[] => [ type: 'index-pattern', updated_at: '2018-08-29T13:22:17.617Z', version: '1', - migrationVersion: {}, attributes: { title: 'kibana_sample_data_logstsdb', name: 'Kibana Sample Data Logs (TSDB)', @@ -382,27 +288,10 @@ export const getSavedObjects = (): SavedObject[] => [ id: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef8f5b', type: 'dashboard', namespaces: ['default'], - updated_at: '2022-09-26T16:24:51.698Z', - version: 'WzE1NTIsMV0=', + updated_at: '2023-03-23T16:25:27.102Z', + created_at: '2023-03-23T16:25:27.102Z', + version: 'WzEzMjAsMV0=', attributes: { - title: i18n.translate('home.sampleData.logsTsdbSpec.webTrafficTitle', { - defaultMessage: '[Logs TSDB] Web Traffic', - }), - hits: 0, - description: i18n.translate('home.sampleData.logsTsdbSpec.webTrafficDescription', { - defaultMessage: "Analyze mock web traffic log data for Elastic's website", - }), - panelsJSON: - '[{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"mapBuffer":{"minLon":-112.5,"minLat":21.94305,"maxLon":-45,"maxLat":55.77657},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_4"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"9"},"panelIndex":"9","embeddableConfig":{"mapCenter":[36.8092847020594,-96.94335937500001],"vis":{"params":{"sort":{"columnIndex":null,"direction":null}}},"enhancements":{}},"panelRefName":"panel_9"},{"version":"8.6.0","type":"visualization","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"vis":{"colors":{"0 - 500":"#BF1B00","1000 - 1500":"#7EB26D","500 - 1000":"#F2C96D"},"defaultColors":{"0 - 500":"rgb(165,0,38)","1000 - 1500":"rgb(0,104,55)","500 - 1000":"rgb(255,255,190)"},"legendOpen":false},"enhancements":{},"hidePanelTitles":true},"title":"","panelRefName":"panel_11"},{"version":"8.6.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_15"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","accessor":"37430d12-7452-4cc9-b035-5cfd4061edf0","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logstsdb\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', - optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', - version: 2, - timeRestore: true, - timeTo: 'now', - timeFrom: 'now-7d', - refreshInterval: { - pause: false, - value: 900000, - }, controlGroupInput: { controlStyle: 'oneLine', chainingSystem: 'HIERARCHICAL', @@ -412,9 +301,26 @@ export const getSavedObjects = (): SavedObject[] => [ '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', }, kibanaSavedObjectMeta: { - searchSourceJSON: - '{"query":{"language":"kuery","query":""},"highlightAll":true,"version":true,"filter":[]}', + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, + description: i18n.translate('home.sampleData.logsTsdbSpec.webTrafficDescription', { + defaultMessage: "Analyze mock web traffic log data for Elastic's website", + }), + refreshInterval: { + pause: false, + value: 900000, }, + timeRestore: true, + optionsJSON: + '{"useMargins":true,"syncColors":false,"syncCursor":true,"syncTooltips":false,"hidePanelTitles":false}', + panelsJSON: + '[{"version":"8.8.0","type":"map","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"hiddenLayers":[],"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"openTOCDetails":[],"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a"}],"state":{"visualization":{"layerId":"28b89898-3feb-415a-8dd9-74d755ac7c2a","layerType":"data","metricAccessor":"f92c482e-1eee-4c2a-9338-64fb3eec286a","palette":{"name":"custom","type":"palette","params":{"steps":3,"name":"custom","reverse":false,"rangeType":"number","rangeMin":0,"rangeMax":null,"progression":"fixed","stops":[{"color":"#D23115","stop":500},{"color":"#FCC400","stop":1000},{"color":"#68BC00","stop":1658}],"colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":500},{"color":"#68BC00","stop":1000}],"continuity":"above","maxSteps":5}}},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"28b89898-3feb-415a-8dd9-74d755ac7c2a":{"columns":{"f92c482e-1eee-4c2a-9338-64fb3eec286a":{"label":"Unique Visitors","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["f92c482e-1eee-4c2a-9338-64fb3eec286a"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"attributes":{"title":"[Logs] Response Codes Over Time + Annotations (converted)","visualizationType":"lnsXY","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82"}],"state":{"visualization":{"legend":{"isVisible":true,"showSingleSeries":true,"position":"bottom","shouldTruncate":true,"maxLines":1},"valueLabels":"hide","fittingFunction":"None","fillOpacity":0.5,"yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"yLeftScale":"linear","yRightScale":"linear","axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"seriesType":"area_percentage_stacked","layerType":"data","layerId":"b38fe501-4b47-4de8-a423-6656d1162174","accessors":["896c5eb2-81c5-44f1-a4a1-57344161ea62"],"yConfig":[{"forAccessor":"896c5eb2-81c5-44f1-a4a1-57344161ea62","color":"rgba(115,216,255,1)","axisMode":"left"}],"xAccessor":"8986e393-d24f-49b0-96ca-118fd66d75e5","splitAccessor":"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","palette":{"name":"default","type":"palette"}},{"layerId":"f265e722-ae38-495c-903c-48aa7931fa82","layerType":"annotations","ignoreGlobalFilters":true,"annotations":[{"type":"query","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","label":"Event","key":{"type":"point_in_time"},"color":"#D33115","timeField":"timestamp","icon":"asterisk","filter":{"type":"kibana_query","query":"tags:error AND tags:security","language":"lucene"},"extraFields":["geo.src"]}]}]},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"b38fe501-4b47-4de8-a423-6656d1162174":{"columns":{"8986e393-d24f-49b0-96ca-118fd66d75e5":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true,"dropPartials":false}},"43f5bb0f-c6da-43a0-8a0a-50e9838ed34b":{"label":"Filters","dataType":"string","operationType":"filters","scale":"ordinal","isBucketed":true,"params":{"filters":[{"input":{"query":"response.keyword >= 200 and response.keyword < 400","language":"kuery"},"label":"HTTP 2xx and 3xx"},{"input":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"label":"HTTP 4xx"},{"input":{"query":"response.keyword >= 500","language":"kuery"},"label":"HTTP 5xx"}]}},"896c5eb2-81c5-44f1-a4a1-57344161ea62":{"label":"Response Code Count","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":true},"customLabel":true}},"columnOrder":["8986e393-d24f-49b0-96ca-118fd66d75e5","43f5bb0f-c6da-43a0-8a0a-50e9838ed34b","896c5eb2-81c5-44f1-a4a1-57344161ea62"],"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{},"hidePanelTitles":false},"title":"[Logs] Response Codes Over Time + Annotations"},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}],"state":{"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","layerType":"data","metricAccessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsMetric","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}],"state":{"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","layerType":"data","metricAccessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","params":{"emptyAsNull":false},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"21bb0939-ee09-4021-8848-6552b3a6a788"},"panelIndex":"21bb0939-ee09-4021-8848-6552b3a6a788","embeddableConfig":{"attributes":{"title":"","visualizationType":"lnsDatatable","type":"lens","references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404"}],"state":{"visualization":{"columns":[{"columnId":"4e64d6d7-4f92-4d5e-abbb-13796604db30","isTransposed":false},{"columnId":"fb9a848d-76f3-4005-a067-4259a50b5621","isTransposed":false},{"columnId":"a2760bc2-9a6e-46a1-8595-86f61573c7cf","isTransposed":false},{"columnId":"2c8bd8d5-35ff-4386-8d27-3ba882b13e43","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#d23115","stop":1000},{"color":"#fcc400","stop":1500},{"color":"#68bc00","stop":1501}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#d23115","stop":0},{"color":"#fcc400","stop":1000},{"color":"#68bc00","stop":1500}],"name":"custom"}}},{"columnId":"defa6f97-b874-4556-8438-056fb437787b","isTransposed":false,"colorMode":"text","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#D23115","stop":10},{"color":"#FCC400","stop":25},{"color":"#68bc00","stop":26}],"rangeType":"number","rangeMin":0,"rangeMax":null,"continuity":"above","colorStops":[{"color":"#D23115","stop":0},{"color":"#FCC400","stop":10},{"color":"#68bc00","stop":25}],"name":"custom"}}}],"layerId":"c840e93e-2949-4723-ad35-6bdb2d724404","layerType":"data"},"query":{"query":"","language":"kuery"},"filters":[],"datasourceStates":{"formBased":{"layers":{"c840e93e-2949-4723-ad35-6bdb2d724404":{"columns":{"4e64d6d7-4f92-4d5e-abbb-13796604db30":{"label":"Type","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"extension.keyword","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"fb9a848d-76f3-4005-a067-4259a50b5621"},"orderDirection":"desc","otherBucket":true,"missingBucket":false,"parentFormat":{"id":"terms"},"include":[],"exclude":[],"includeIsRegex":false,"excludeIsRegex":false},"customLabel":true},"fb9a848d-76f3-4005-a067-4259a50b5621":{"label":"Bytes (Total)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"a2760bc2-9a6e-46a1-8595-86f61573c7cf":{"label":"Bytes (Last Hour)","dataType":"number","operationType":"sum","sourceField":"bytes","isBucketed":false,"scale":"ratio","reducedTimeRange":"1h","params":{"emptyAsNull":true,"format":{"id":"bytes","params":{"decimals":2}}},"customLabel":true},"2c8bd8d5-35ff-4386-8d27-3ba882b13e43":{"label":"Unique Visits (Total)","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"params":{"emptyAsNull":true},"customLabel":true},"defa6f97-b874-4556-8438-056fb437787b":{"label":"Unique count of clientip","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"reducedTimeRange":"1h","params":{"emptyAsNull":true}}},"columnOrder":["4e64d6d7-4f92-4d5e-abbb-13796604db30","fb9a848d-76f3-4005-a067-4259a50b5621","a2760bc2-9a6e-46a1-8595-86f61573c7cf","2c8bd8d5-35ff-4386-8d27-3ba882b13e43","defa6f97-b874-4556-8438-056fb437787b"],"sampling":1,"incompleteColumns":{}}}},"textBased":{"layers":{}}},"internalReferences":[],"adHocDataViews":{}}},"enhancements":{}}},{"version":"8.8.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.8.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"formBased":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0c247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', + timeFrom: 'now-7d/d', + title: i18n.translate('home.sampleData.logsTsdbSpec.webTrafficTitle', { + defaultMessage: '[Logs TSDB] Web Traffic', + }), + timeTo: 'now', + version: 1, }, references: [ { @@ -423,14 +329,9 @@ export const getSavedObjects = (): SavedObject[] => [ id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', }, { - name: '9:panel_9', - type: 'visualization', - id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b', - }, - { - name: '11:panel_11', - type: 'visualization', - id: '69a34b00-9ee8-11e7-8711-e7a007dcef99', + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0c247', + name: '11:indexpattern-datasource-layer-28b89898-3feb-415a-8dd9-74d755ac7c2a', }, { name: '14:panel_14', @@ -438,45 +339,40 @@ export const getSavedObjects = (): SavedObject[] => [ id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', }, { - name: '15:panel_15', - type: 'visualization', - id: '314c6f60-2224-11e8-b802-5bcf64c2cfb4', + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0c247', + name: '15:indexpattern-datasource-layer-b38fe501-4b47-4de8-a423-6656d1162174', }, { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: 'bb94016e-f4a6-49ca-87a9-296a2869d570:indexpattern-datasource-current-indexpattern', type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0c247', + name: '15:xy-visualization-layer-f265e722-ae38-495c-903c-48aa7931fa82', }, { + type: 'index-pattern', id: '90943e30-9a47-11e8-b64d-95841ca0c247', name: 'bb94016e-f4a6-49ca-87a9-296a2869d570:indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed', - type: 'index-pattern', }, { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: '01d8e435-91c0-484f-a11e-856747050b0a:indexpattern-datasource-current-indexpattern', type: 'index-pattern', - }, - { id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: '01d8e435-91c0-484f-a11e-856747050b0a:indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a', - type: 'index-pattern', + name: '8c1456d4-1993-4ba2-b701-04aca02c9fef:indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a', }, { - id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: '8c1456d4-1993-4ba2-b701-04aca02c9fef:indexpattern-datasource-current-indexpattern', type: 'index-pattern', - }, - { id: '90943e30-9a47-11e8-b64d-95841ca0c247', - name: '8c1456d4-1993-4ba2-b701-04aca02c9fef:indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a', - type: 'index-pattern', + name: '01d8e435-91c0-484f-a11e-856747050b0a:indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a', }, { name: '8e59c7cf-6e42-4343-a113-c4a255fcf2ce:panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce', type: 'visualization', id: 'cb099a20-ea66-11eb-9425-113343a037e3', }, + { + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0c247', + name: '21bb0939-ee09-4021-8848-6552b3a6a788:indexpattern-datasource-layer-c840e93e-2949-4723-ad35-6bdb2d724404', + }, { name: 'cbca842c-b9fa-4523-9ce0-14e350866e33:panel_cbca842c-b9fa-4523-9ce0-14e350866e33', type: 'lens', @@ -508,19 +404,18 @@ export const getSavedObjects = (): SavedObject[] => [ id: '90943e30-9a47-11e8-b64d-95841ca0c247', }, ], - migrationVersion: { - dashboard: '8.5.0', - }, - coreMigrationVersion: '8.6.0', + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '8.7.0', + managed: false, }, { - id: '2f360f30-ea74-11eb-b4c6-3d2afc1cc389', + id: '2f360f30-ea74-11eb-b4c6-3d2afc1cb389', type: 'search', updated_at: '2021-07-21T22:37:09.415Z', version: '1', - migrationVersion: { - search: '7.9.3', - }, + coreMigrationVersion: '8.8.0', + typeMigrationVersion: '7.9.3', + managed: false, attributes: { title: i18n.translate('home.sampleData.logsSpec.discoverTitle', { defaultMessage: '[Logs] Visits', diff --git a/tsconfig.base.json b/tsconfig.base.json index 08afa3562fdd2..10f8f78781437 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -770,6 +770,8 @@ "@kbn/expressions-plugin/*": ["src/plugins/expressions/*"], "@kbn/failed-test-reporter-cli": ["packages/kbn-failed-test-reporter-cli"], "@kbn/failed-test-reporter-cli/*": ["packages/kbn-failed-test-reporter-cli/*"], + "@kbn/feature-controls-examples-plugin": ["examples/feature_control_examples"], + "@kbn/feature-controls-examples-plugin/*": ["examples/feature_control_examples/*"], "@kbn/feature-usage-test-plugin": ["x-pack/test/plugin_api_integration/plugins/feature_usage_test"], "@kbn/feature-usage-test-plugin/*": ["x-pack/test/plugin_api_integration/plugins/feature_usage_test/*"], "@kbn/features-plugin": ["x-pack/plugins/features"], diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/index.ts new file mode 100644 index 0000000000000..26d14465471cf --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/index.ts @@ -0,0 +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. + */ + +export { muteAlertParamsSchema } from './schemas/latest'; +export { muteAlertParamsSchema as muteAlertParamsSchemaV1 } from './schemas/v1'; + +export type { MuteAlertRequestParams } from './types/latest'; +export type { MuteAlertRequestParams as MuteAlertRequestParamsV1 } from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/latest.ts new file mode 100644 index 0000000000000..eee4b4b4e34b3 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/latest.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { muteAlertParamsSchema } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/v1.ts new file mode 100644 index 0000000000000..3cfe34de957e2 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/schemas/v1.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const muteAlertParamsSchema = schema.object({ + rule_id: schema.string(), + alert_id: schema.string(), +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/latest.ts new file mode 100644 index 0000000000000..0313b54c87630 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { MuteAlertRequestParams } from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/v1.ts new file mode 100644 index 0000000000000..af3832641d530 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/mute_alert/types/v1.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { TypeOf } from '@kbn/config-schema'; +import { muteAlertParamsSchemaV1 } from '..'; + +export type MuteAlertRequestParams = TypeOf; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/mute_instance.ts similarity index 61% rename from x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts rename to x-pack/plugins/alerting/server/application/rule/methods/mute_alert/mute_instance.ts index 5f37988b7b718..8adbdf7ae58c9 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/mute_instance.ts @@ -5,28 +5,37 @@ * 2.0. */ -import { Rule } from '../../types'; -import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { retryIfConflicts } from '../../lib/retry_if_conflicts'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { MuteOptions } from '../types'; -import { RulesClientContext } from '../types'; -import { updateMeta } from '../lib'; +import Boom from '@hapi/boom'; +import { updateRuleSo } from '../../../../data/rule/methods/update_rule_so'; +import { muteAlertParamsSchema } from './schemas'; +import type { MuteAlertParams } from './types'; +import { Rule } from '../../../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization'; +import { retryIfConflicts } from '../../../../lib/retry_if_conflicts'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { RulesClientContext } from '../../../../rules_client/types'; +import { updateMeta } from '../../../../rules_client/lib'; export async function muteInstance( context: RulesClientContext, - { alertId, alertInstanceId }: MuteOptions + params: MuteAlertParams ): Promise { + try { + muteAlertParamsSchema.validate(params); + } catch (error) { + throw Boom.badRequest(`Failed to validate params: ${error.message}`); + } + return await retryIfConflicts( context.logger, - `rulesClient.muteInstance('${alertId}')`, - async () => await muteInstanceWithOCC(context, { alertId, alertInstanceId }) + `rulesClient.muteInstance('${params.alertId}')`, + async () => await muteInstanceWithOCC(context, params) ); } async function muteInstanceWithOCC( context: RulesClientContext, - { alertId, alertInstanceId }: MuteOptions + { alertId, alertInstanceId }: MuteAlertParams ) { const { attributes, version } = await context.unsecuredSavedObjectsClient.get( 'alert', @@ -68,15 +77,15 @@ async function muteInstanceWithOCC( const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { mutedInstanceIds.push(alertInstanceId); - await context.unsecuredSavedObjectsClient.update( - 'alert', - alertId, - updateMeta(context, { + await updateRuleSo({ + savedObjectsClient: context.unsecuredSavedObjectsClient, + savedObjectsUpdateOptions: { version }, + id: alertId, + updateRuleAttributes: updateMeta(context, { mutedInstanceIds, updatedBy: await context.getUserName(), updatedAt: new Date().toISOString(), }), - { version } - ); + }); } } diff --git a/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/index.ts new file mode 100644 index 0000000000000..e7148adf7eefe --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { muteAlertParamsSchema } from './mute_alert_params_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/mute_alert_params_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/mute_alert_params_schema.ts new file mode 100644 index 0000000000000..6c8df8cb907da --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/schemas/mute_alert_params_schema.ts @@ -0,0 +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. + */ +import { schema } from '@kbn/config-schema'; + +export const muteAlertParamsSchema = schema.object({ + alertId: schema.string(), + alertInstanceId: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/index.ts new file mode 100644 index 0000000000000..8b72247e15649 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export type { MuteAlertParams } from './mute_alert_params'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/mute_alert_params.ts b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/mute_alert_params.ts new file mode 100644 index 0000000000000..f94f454f1f78c --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/mute_alert/types/mute_alert_params.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { muteAlertParamsSchema } from '../schemas'; + +export type MuteAlertParams = TypeOf; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index a93803ed6d585..5086cb56279ed 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -32,7 +32,7 @@ import { healthRoute } from './health'; import { resolveRuleRoute } from './resolve_rule'; import { ruleTypesRoute } from './rule_types'; import { muteAllRuleRoute } from './mute_all_rule'; -import { muteAlertRoute } from './mute_alert'; +import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert'; import { unmuteAllRuleRoute } from './unmute_all_rule'; import { unmuteAlertRoute } from './unmute_alert'; import { updateRuleApiKeyRoute } from './update_rule_api_key'; diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.test.ts similarity index 87% rename from x-pack/plugins/alerting/server/routes/mute_alert.test.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.test.ts index ef67a6d2ef3bf..440c040d74ff7 100644 --- a/x-pack/plugins/alerting/server/routes/mute_alert.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.test.ts @@ -7,13 +7,13 @@ import { muteAlertRoute } from './mute_alert'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { RuleTypeDisabledError } from '../../../../lib'; const rulesClient = rulesClientMock.create(); -jest.mock('../lib/license_api_access', () => ({ +jest.mock('../../../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/mute_alert.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.ts similarity index 62% rename from x-pack/plugins/alerting/server/routes/mute_alert.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.ts index 22d9a1670dde5..45a0a37f3164c 100644 --- a/x-pack/plugins/alerting/server/routes/mute_alert.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/mute_alert.ts @@ -4,26 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { ILicenseState, RuleTypeDisabledError } from '../lib'; -import { MuteOptions } from '../rules_client'; -import { RewriteRequestCase, verifyAccessAndContext } from './lib'; -import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; - -const paramSchema = schema.object({ - rule_id: schema.string(), - alert_id: schema.string(), -}); - -const rewriteParamsReq: RewriteRequestCase = ({ - rule_id: alertId, - alert_id: alertInstanceId, -}) => ({ - alertId, - alertInstanceId, -}); +import { transformRequestParamsToApplicationV1 } from './transforms'; +import { ILicenseState, RuleTypeDisabledError } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../../../../types'; +import { + muteAlertParamsSchemaV1, + MuteAlertRequestParamsV1, +} from '../../../../../common/routes/rule/apis/mute_alert'; export const muteAlertRoute = ( router: IRouter, @@ -33,15 +22,15 @@ export const muteAlertRoute = ( { path: `${BASE_ALERTING_API_PATH}/rule/{rule_id}/alert/{alert_id}/_mute`, validate: { - params: paramSchema, + params: muteAlertParamsSchemaV1, }, }, router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); - const params = rewriteParamsReq(req.params); + const params: MuteAlertRequestParamsV1 = req.params; try { - await rulesClient.muteInstance(params); + await rulesClient.muteInstance(transformRequestParamsToApplicationV1(params)); return res.noContent(); } catch (e) { if (e instanceof RuleTypeDisabledError) { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/index.ts new file mode 100644 index 0000000000000..21a7250aed4e2 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ +export { transformRequestParamsToApplication } from './transform_request_params_to_application/latest'; +export { transformRequestParamsToApplication as transformRequestParamsToApplicationV1 } from './transform_request_params_to_application/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/latest.ts new file mode 100644 index 0000000000000..5983069f0d8fd --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/latest.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { transformRequestParamsToApplication } from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/v1.ts new file mode 100644 index 0000000000000..37966060dba02 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/mute_alert/transforms/transform_request_params_to_application/v1.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MuteAlertParams } from '../../../../../../application/rule/methods/mute_alert/types'; +import { RewriteRequestCase } from '../../../../../lib'; + +export const transformRequestParamsToApplication: RewriteRequestCase = ({ + rule_id: alertId, + alert_id: alertInstanceId, +}) => ({ + alertId, + alertInstanceId, +}); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 4c1138f23cb39..d7a576ac99d0b 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -5,9 +5,10 @@ * 2.0. */ +import { MuteAlertParams } from '../application/rule/methods/mute_alert/types'; import { SanitizedRule, RuleTypeParams } from '../types'; import { parseDuration } from '../../common/parse_duration'; -import { RulesClientContext, BulkOptions, MuteOptions } from './types'; +import { RulesClientContext, BulkOptions } from './types'; import { clone, CloneArguments } from './methods/clone'; import { createRule, CreateRuleParams } from '../application/rule/methods/create'; import { get, GetParams } from './methods/get'; @@ -52,9 +53,9 @@ import { disable } from './methods/disable'; import { snooze, SnoozeParams } from './methods/snooze'; import { unsnooze, UnsnoozeParams } from './methods/unsnooze'; import { clearExpiredSnoozes } from './methods/clear_expired_snoozes'; +import { muteInstance } from '../application/rule/methods/mute_alert/mute_instance'; import { muteAll } from './methods/mute_all'; import { unmuteAll } from './methods/unmute_all'; -import { muteInstance } from './methods/mute_instance'; import { unmuteInstance } from './methods/unmute_instance'; import { runSoon } from './methods/run_soon'; import { listRuleTypes } from './methods/list_rule_types'; @@ -163,8 +164,8 @@ export class RulesClient { public muteAll = (options: { id: string }) => muteAll(this.context, options); public unmuteAll = (options: { id: string }) => unmuteAll(this.context, options); - public muteInstance = (options: MuteOptions) => muteInstance(this.context, options); - public unmuteInstance = (options: MuteOptions) => unmuteInstance(this.context, options); + public muteInstance = (options: MuteAlertParams) => muteInstance(this.context, options); + public unmuteInstance = (options: MuteAlertParams) => unmuteInstance(this.context, options); public runSoon = (options: { id: string }) => runSoon(this.context, options); diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index c755651f5524d..87ae594976246 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -121,6 +121,7 @@ export interface IndexType { [key: string]: unknown; } +// TODO: remove once all mute endpoints have been migrated to RuleMuteAlertOptions export interface MuteOptions extends IndexType { alertId: string; alertInstanceId: string; diff --git a/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts b/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts index 2b1088990334c..68ed4baaeb5c8 100644 --- a/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts +++ b/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts @@ -47,12 +47,15 @@ export function hostsRoutes({ async (context, req, res) => { const { from = 'now-24h', to = 'now' } = req.query || {}; const esClient = await getEsClientFromContext(context); + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; try { const response = await assetAccessor.getHosts({ from: datemath.parse(from)!.toISOString(), to: datemath.parse(to)!.toISOString(), esClient, + soClient, }); return res.ok({ body: response }); diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 5dd5e56b2c3a1..02444bd6efa72 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -94,7 +94,7 @@ interface PluginsSetup { usageCollection?: UsageCollectionSetup; } -interface PluginsStart { +export interface PluginsStart { data: DataPluginStart; security: SecurityPluginStart; spaces?: SpacesPluginStart; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_topics.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_topics.tsx index 9360a5c35bcd5..58cff89ac8c8f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_topics.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_topics.tsx @@ -18,9 +18,11 @@ import { EuiFormErrorText, EuiFormRow, EuiIcon, + EuiLink, EuiPanel, EuiSelect, EuiSpacer, + EuiText, EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -32,6 +34,8 @@ import styled, { useTheme } from 'styled-components'; import type { EuiTheme } from '@kbn/kibana-react-plugin/common'; +import { useStartServices } from '../../../../../../hooks'; + import type { KafkaTopicWhenType, ValueOf } from '../../../../../../../common/types'; import { kafkaTopicWhenType } from '../../../../../../../common/constants'; @@ -49,6 +53,7 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm } = inputs.kafkaTopicsInput; const theme = useTheme() as EuiTheme; const [autoFocus, setAutoFocus] = useState(false); + const { docLinks } = useStartServices(); const indexedErrors = useMemo(() => { if (!errors) { @@ -214,10 +219,27 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm + <> + + + + + documentation + + ), + }} + /> + + + } > {topics.length > 1 ? ( @@ -232,7 +254,6 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm spacing="m" index={index} draggableId={`${id}${index}Draggable`} - // isDragDisabled={disabled} customDragHandle={true} style={{ paddingLeft: 0, @@ -348,7 +369,7 @@ export const OutputFormKafkaTopics: React.FunctionComponent<{ inputs: OutputForm const topicConditionErrors = indexedConditionErrors[index]; return ( <> - + diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 9107a334dad83..2cdd04483e108 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -363,7 +363,7 @@ export function transformOutputToFullPolicyOutput( topic: topicName, when: { [rest.when.type as string]: { - [keyName.replace(/\s/g, '')]: value.replace(/\s/g, ''), + [keyName.replace(/\s/g, '')]: value, }, }, }; diff --git a/x-pack/plugins/ml/public/mocks.ts b/x-pack/plugins/ml/public/mocks.ts index 77cdefdb2f1c9..be18bfb1f49f1 100644 --- a/x-pack/plugins/ml/public/mocks.ts +++ b/x-pack/plugins/ml/public/mocks.ts @@ -9,30 +9,33 @@ import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { type ElasticModels } from './application/services/elastic_models_service'; import type { MlPluginSetup, MlPluginStart } from './plugin'; +const createElasticModelsMock = (): jest.Mocked => { + return { + getELSER: jest.fn().mockResolvedValue({ + version: 2, + default: true, + config: { + input: { + field_names: ['text_field'], + }, + }, + description: 'Elastic Learned Sparse EncodeR v2 (Tech Preview)', + name: '.elser_model_2', + }), + } as unknown as jest.Mocked; +}; + const createSetupContract = (): jest.Mocked => { return { locator: sharePluginMock.createLocator(), + elasticModels: createElasticModelsMock(), }; }; const createStartContract = (): jest.Mocked => { return { locator: sharePluginMock.createLocator(), - elasticModels: { - getELSER: jest.fn(() => - Promise.resolve({ - version: 2, - default: true, - config: { - input: { - field_names: ['text_field'], - }, - }, - description: 'Elastic Learned Sparse EncodeR v2 (Tech Preview)', - name: '.elser_model_2', - }) - ), - } as unknown as jest.Mocked, + elasticModels: createElasticModelsMock(), }; }; diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 725bf422c58c6..fcd1948e57581 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -18,7 +18,7 @@ import { take } from 'rxjs/operators'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { ManagementSetup } from '@kbn/management-plugin/public'; -import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import type { LocatorPublic, SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; @@ -54,7 +54,7 @@ import { MlSharedServices, } from './application/services/get_shared_ml_services'; import { registerManagementSection } from './application/management'; -import { MlLocatorDefinition, type MlLocator } from './locator'; +import { MlLocatorDefinition, MlLocatorParams, type MlLocator } from './locator'; import { setDependencyCache } from './application/util/dependency_cache'; import { registerHomeFeature } from './register_home_feature'; import { isFullLicense, isMlEnabled } from '../common/license'; @@ -67,6 +67,7 @@ import { type ConfigSchema, } from '../common/constants/app'; import type { MlCapabilities } from './shared'; +import { ElasticModels } from './application/services/elastic_models_service'; export interface MlStartDependencies { dataViewEditor: DataViewEditorStart; @@ -131,7 +132,10 @@ export class MlPlugin implements Plugin { initEnabledFeatures(this.enabledFeatures, initializerContext.config.get()); } - setup(core: MlCoreSetup, pluginsSetup: MlSetupDependencies) { + setup( + core: MlCoreSetup, + pluginsSetup: MlSetupDependencies + ): { locator?: LocatorPublic; elasticModels?: ElasticModels } { this.sharedMlServices = getMlSharedServices(core.http); core.application.register({ @@ -262,10 +266,14 @@ export class MlPlugin implements Plugin { return { locator: this.locator, + elasticModels: this.sharedMlServices.elasticModels, }; } - start(core: CoreStart, deps: MlStartDependencies) { + start( + core: CoreStart, + deps: MlStartDependencies + ): { locator?: LocatorPublic; elasticModels?: ElasticModels } { setDependencyCache({ docLinks: core.docLinks!, basePath: core.http.basePath, diff --git a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx index d9456e6ed253b..219bde55880cf 100644 --- a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx +++ b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import useObservable from 'react-use/lib/useObservable'; -import { type BehaviorSubject, distinctUntilChanged } from 'rxjs'; +import { type BehaviorSubject, distinctUntilChanged, filter, take } from 'rxjs'; import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; import { AppMountParameters } from '@kbn/core-application-browser'; import { @@ -111,6 +111,16 @@ const StatefulTopNav = ({ }: LogExplorerTopNavMenuProps) => { const { euiTheme } = useEuiTheme(); + /** + * Since the breadcrumbsAppendExtension might be set only during a plugin start (e.g. search session) + * we retrieve the latest valid extension in order to restore it once we unmount the beta badge. + */ + const [previousAppendExtension$] = useState(() => + services.chrome.getBreadcrumbsAppendExtension$().pipe(filter(Boolean), take(1)) + ); + + const previousAppendExtension = useObservable(previousAppendExtension$); + useEffect(() => { const { chrome, i18n, theme } = services; @@ -137,7 +147,13 @@ const StatefulTopNav = ({ ), }); } - }, [euiTheme, services]); + + return () => { + if (chrome) { + chrome.setBreadcrumbsAppendExtension(previousAppendExtension); + } + }; + }, [euiTheme, services, previousAppendExtension]); return ( diff --git a/x-pack/plugins/observability_log_explorer/public/plugin.ts b/x-pack/plugins/observability_log_explorer/public/plugin.ts index e82d863727a60..0bf10b5fab5b1 100644 --- a/x-pack/plugins/observability_log_explorer/public/plugin.ts +++ b/x-pack/plugins/observability_log_explorer/public/plugin.ts @@ -55,6 +55,7 @@ export class ObservabilityLogExplorerPlugin ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden, searchable: true, + keywords: ['logs', 'log', 'explorer', 'logs explorer'], mount: async (appMountParams) => { const [coreStart, pluginsStart, ownPluginStart] = await core.getStartServices(); diff --git a/x-pack/plugins/observability_log_explorer/server/config.ts b/x-pack/plugins/observability_log_explorer/server/config.ts index 1977c5e625c15..9718345a72a25 100644 --- a/x-pack/plugins/observability_log_explorer/server/config.ts +++ b/x-pack/plugins/observability_log_explorer/server/config.ts @@ -10,6 +10,7 @@ import { PluginConfigDescriptor } from '@kbn/core/server'; import { ObservabilityLogExplorerConfig } from '../common/plugin_config'; export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), navigation: schema.object({ showAppLink: offeringBasedSchema({ serverless: schema.boolean({ diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx index 5b6db7f900b79..922736ea107e5 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx @@ -41,6 +41,8 @@ import { BackButton } from './back_button'; import { WindowsInstallStep } from '../../shared/windows_install_step'; import { TroubleshootingLink } from '../../shared/troubleshooting_link'; +const defaultDatasetName = ''; + export function InstallElasticAgent() { const { services: { share }, @@ -63,7 +65,8 @@ export function InstallElasticAgent() { useState('linux-tar'); const enforcedDatasetName = - (integration === dataset ? dataset : `${integration}.${dataset}`) || ''; + (integration === dataset ? dataset : `${integration}.${dataset}`) ?? + defaultDatasetName; async function onContinue() { await singleDatasetLocator!.navigate({ @@ -109,11 +112,15 @@ export function InstallElasticAgent() { customConfigurations, logFilePaths, } = getState(); - if (!hasAlreadySavedFlow(getState()) && monitoringRole?.hasPrivileges) { + if ( + !hasAlreadySavedFlow(getState()) && + monitoringRole?.hasPrivileges && + datasetName + ) { return callApi('POST /internal/observability_onboarding/logs/flow', { params: { body: { - name: datasetName || '', + name: datasetName, type: 'logFiles', state: { datasetName, diff --git a/x-pack/plugins/observability_onboarding/public/index.ts b/x-pack/plugins/observability_onboarding/public/index.ts index 2b8271bb5c8ed..9454626e80dc8 100644 --- a/x-pack/plugins/observability_onboarding/public/index.ts +++ b/x-pack/plugins/observability_onboarding/public/index.ts @@ -17,9 +17,6 @@ import { ObservabilityOnboardingPluginStart, } from './plugin'; -export type { OBSERVABILITY_ONBOARDING_LOCATOR } from './locators/onboarding_locator/locator_definition'; -export type { ObservabilityOnboardingLocatorParams } from './locators/onboarding_locator/types'; - export interface ConfigSchema { ui: { enabled: boolean; diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts index 93bb3861740cd..9ebd263847f36 100644 --- a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/get_location.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ObservabilityOnboardingLocatorParams } from './types'; +import type { ObservabilityOnboardingLocatorParams } from '@kbn/deeplinks-observability/locators'; import { PLUGIN_ID } from '../../../common'; export function getLocation(params: ObservabilityOnboardingLocatorParams) { diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index 9f08cab70b064..c74ca9d607dc1 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -59,6 +59,11 @@ const euiProgressCss = { marginTop: '-2px', }; +const resultsTableContainerCss = { + width: '100%', + maxWidth: '1200px', +}; + export interface ResultsTableComponentProps { actionId: string; selectedAgent?: string; @@ -435,19 +440,21 @@ const ResultsTableComponent: React.FC = ({ ) : ( - +
+ +
)} diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts index 6e95c1ad73557..c8db6ff223ff4 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts @@ -61,6 +61,7 @@ describe( cy.getByTestSubj('protection-updates-manifest-switch'); cy.getByTestSubj('protection-updates-manifest-name-title'); cy.getByTestSubj('protection-updates-manifest-name'); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); cy.getByTestSubj('protection-updates-manifest-switch').click(); @@ -72,17 +73,18 @@ describe( }); cy.getByTestSubj('protection-updates-manifest-name-note-title'); cy.getByTestSubj('protection-updates-manifest-note'); - cy.getByTestSubj('policyDetailsSaveButton'); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.enabled'); }); it('should successfully update the manifest version to custom date', () => { loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); cy.getByTestSubj('protection-updates-manifest-switch').click(); cy.getByTestSubj('protection-updates-manifest-note').type(testNote); cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy'); - cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note'); - cy.getByTestSubj('policyDetailsSaveButton').click(); + cy.intercept('POST', `/api/endpoint/protection_updates_note/${policy.id}`).as('note'); + cy.getByTestSubj('protectionUpdatesSaveButton').click(); cy.wait('@policy').then(({ request, response }) => { expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( today.format('YYYY-MM-DD') @@ -98,6 +100,7 @@ describe( cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); }); @@ -131,9 +134,13 @@ describe( it('should update manifest version to latest when enabling automatic updates', () => { loadProtectionUpdatesUrl(policy.id); cy.getByTestSubj('protection-updates-manifest-outdated'); - cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy_latest'); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); cy.getByTestSubj('protection-updates-manifest-switch').click(); + cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy_latest'); + + cy.getByTestSubj('protectionUpdatesSaveButton').click(); + cy.wait('@policy_latest').then(({ request, response }) => { expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( 'latest' @@ -142,6 +149,7 @@ describe( }); cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); cy.getByTestSubj('protection-updates-automatic-updates-enabled'); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); }); @@ -175,18 +183,23 @@ describe( it('should update note on save', () => { loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); + cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); cy.getByTestSubj('protection-updates-manifest-note').clear(); cy.getByTestSubj('protection-updates-manifest-note').type(updatedTestNote); - cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note_updated'); - cy.getByTestSubj('policyDetailsSaveButton').click(); + cy.intercept('POST', `/api/endpoint/protection_updates_note/${policy.id}`).as( + 'note_updated' + ); + cy.getByTestSubj('protectionUpdatesSaveButton').click(); cy.wait('@note_updated').then(({ request, response }) => { expect(request.body.note).to.equal(updatedTestNote); expect(response?.statusCode).to.equal(200); }); cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); cy.getByTestSubj('protection-updates-manifest-note').contains(updatedTestNote); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); }); @@ -238,7 +251,7 @@ describe( cy.getByTestSubj('protection-updates-manifest-name-note-title'); cy.getByTestSubj('protection-updates-manifest-note').should('not.exist'); cy.getByTestSubj('protection-updates-manifest-note-view-mode').contains(testNote); - cy.getByTestSubj('policyDetailsSaveButton').should('be.disabled'); + cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/components/protection_updates_bottom_bar.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/components/protection_updates_bottom_bar.tsx new file mode 100644 index 0000000000000..da9ec04799d6f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/components/protection_updates_bottom_bar.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPageTemplate, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import { useNavigateToAppEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; +import { APP_UI_ID } from '../../../../../../../common'; +import { getPoliciesPath } from '../../../../../common/routing'; +import type { PolicyDetailsRouteState } from '../../../../../../../common/endpoint/types'; + +interface ProtectionUpdatesBottomBarProps { + saveButtonDisabled: boolean; + isUpdating: boolean; + onSave: () => void; +} + +export const ProtectionUpdatesBottomBar = React.memo( + ({ isUpdating, onSave, saveButtonDisabled }) => { + const { state: locationRouteState } = useLocation(); + const [routeState, setRouteState] = useState(); + const routingOnCancelNavigateTo = routeState?.onCancelNavigateTo; + + useEffect(() => { + if (!routeState && locationRouteState) { + setRouteState(locationRouteState); + } + }, [locationRouteState, routeState]); + + const navigateToAppArguments = useMemo((): Parameters => { + if (routingOnCancelNavigateTo) { + return routingOnCancelNavigateTo; + } + + return [ + APP_UI_ID, + { + path: getPoliciesPath(), + }, + ]; + }, [routingOnCancelNavigateTo]); + + const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments); + + return ( + + + + + + + + + + + + + + + ); + } +); + +ProtectionUpdatesBottomBar.displayName = 'ProtectionUpdatesBottomBar'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts index 16b6ee66f07ca..abe21b420dc83 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/hooks/use_post_protection_updates_note.ts @@ -32,7 +32,7 @@ export const useCreateProtectionUpdatesNote = ({ >( (payload) => http.post( - resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { policy_id: packagePolicyId }), + resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { package_policy_id: packagePolicyId }), { version: '2023-10-31', body: JSON.stringify(payload), diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx index eaac2595c931b..54445f6ff5fdd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx @@ -7,7 +7,6 @@ import type { EuiSwitchEvent } from '@elastic/eui'; import { - EuiButton, EuiCallOut, EuiDatePicker, EuiFlexGroup, @@ -30,6 +29,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { Moment } from 'moment'; import moment from 'moment'; import { cloneDeep } from 'lodash'; +import { ProtectionUpdatesBottomBar } from './components/protection_updates_bottom_bar'; import { useCreateProtectionUpdatesNote } from './hooks/use_post_protection_updates_note'; import { useGetProtectionUpdatesNote } from './hooks/use_get_protection_updates_note'; import { useUserPrivileges } from '../../../../../common/components/user_privileges'; @@ -95,70 +95,80 @@ export const ProtectionUpdatesLayout = React.memo( ? AUTOMATIC_UPDATES_CHECKBOX_LABEL : AUTOMATIC_UPDATES_OFF_CHECKBOX_LABEL; - const onSave = useCallback( - (version: string) => { - const update = cloneDeep(policy); - update.inputs[0].config.policy.value.global_manifest_version = version; - sendPolicyUpdate({ policy: update }) - .then(({ item: policyItem }) => { - toasts.addSuccess({ - 'data-test-subj': 'protectionUpdatesSuccessfulMessage', - title: i18n.translate( - 'xpack.securitySolution.endpoint.protectionUpdates.updateSuccessTitle', - { - defaultMessage: 'Success!', - } - ), - text: i18n.translate( - 'xpack.securitySolution.endpoint.protectionUpdates.updateSuccessMessage', - { - defaultMessage: 'Manifest updates successfully saved', - } - ), - }); + const saveButtonEnabled = + (fetchedNote ? note !== fetchedNote.note : note !== '') || + manifestVersion !== deployedVersion; - // Since the 'policyItem' is stored in a store and fetched as a result of an action on urlChange, we still need to dispatch an action even though Redux was removed from this component. - dispatch({ - type: 'serverReturnedPolicyDetailsData', - payload: { - policyItem, - }, - }); - }) - .catch((err) => { - toasts.addDanger({ - 'data-test-subj': 'protectionUpdatesFailureMessage', - title: i18n.translate( - 'xpack.securitySolution.endpoint.protectionUpdates.updateErrorTitle', - { - defaultMessage: 'Failed!', - } - ), - text: err.message, - }); + const onSave = useCallback(() => { + const update = cloneDeep(policy); + update.inputs[0].config.policy.value.global_manifest_version = manifestVersion; + sendPolicyUpdate({ policy: update }) + .then(({ item: policyItem }) => { + toasts.addSuccess({ + 'data-test-subj': 'protectionUpdatesSuccessfulMessage', + title: i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.updateSuccessTitle', + { + defaultMessage: 'Success!', + } + ), + text: i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.updateSuccessMessage', + { + defaultMessage: 'Manifest updates successfully saved', + } + ), }); - if ((!fetchedNote && note !== '') || (fetchedNote && note !== fetchedNote.note)) { - createNote( - { note }, - { - onError: (error) => { - toasts.addDanger({ - 'data-test-subj': 'protectionUpdatesNoteUpdateFailureMessage', - title: i18n.translate( - 'xpack.securitySolution.endpoint.protectionUpdates.noteUpdateErrorTitle', - { - defaultMessage: 'Note update failed!', - } - ), - text: error.body.message, - }); - }, - } - ); - } - }, - [policy, sendPolicyUpdate, fetchedNote, note, toasts, dispatch, createNote] - ); + + // Since the 'policyItem' is stored in a store and fetched as a result of an action on urlChange, we still need to dispatch an action even though Redux was removed from this component. + dispatch({ + type: 'serverReturnedPolicyDetailsData', + payload: { + policyItem, + }, + }); + }) + .catch((err) => { + toasts.addDanger({ + 'data-test-subj': 'protectionUpdatesFailureMessage', + title: i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.updateErrorTitle', + { + defaultMessage: 'Failed!', + } + ), + text: err.message, + }); + }); + if ((!fetchedNote && note !== '') || (fetchedNote && note !== fetchedNote.note)) { + createNote( + { note }, + { + onError: (error) => { + toasts.addDanger({ + 'data-test-subj': 'protectionUpdatesNoteUpdateFailureMessage', + title: i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.noteUpdateErrorTitle', + { + defaultMessage: 'Note update failed!', + } + ), + text: error.body.message, + }); + }, + } + ); + } + }, [ + policy, + manifestVersion, + sendPolicyUpdate, + fetchedNote, + note, + toasts, + dispatch, + createNote, + ]); const toggleAutomaticUpdates = useCallback( (event: EuiSwitchEvent) => { @@ -170,15 +180,11 @@ export const ProtectionUpdatesLayout = React.memo( if (selectedDate !== today) { setSelectedDate(today); } - // We need to save the policy without the user clicking save button - if (deployedVersion !== 'latest') { - onSave('latest'); - } } else { setManifestVersion(selectedDate.format(internalDateFormat)); } }, - [automaticUpdatesEnabled, deployedVersion, onSave, selectedDate, today] + [automaticUpdatesEnabled, selectedDate, today] ); const renderVersionToDeployPicker = () => { @@ -333,7 +339,7 @@ export const ProtectionUpdatesLayout = React.memo( value={note} disabled={getNoteInProgress || createNoteInProgress} onChange={(e) => setNote(e.target.value)} - fullWidth={true} + fullWidth rows={3} placeholder={i18n.translate( 'xpack.securitySolution.endpoint.protectionUpdates.note.placeholder', @@ -346,75 +352,70 @@ export const ProtectionUpdatesLayout = React.memo( ) : ( {note} )} - - - onSave(manifestVersion)} - isLoading={isUpdating} - > - - ); }; return ( - - + - - -
- {i18n.translate('xpack.securitySolution.endpoint.protectionUpdates.manifestName', { - defaultMessage: 'Manifest name', - })} -
-
- - {'artifactsec'} - -
- - {canWritePolicyManagement ? ( - - ) : ( - - {viewModeSwitchLabel} + + + +
+ {i18n.translate( + 'xpack.securitySolution.endpoint.protectionUpdates.manifestName', + { + defaultMessage: 'Manifest name', + } + )} +
+
+ + {'artifactsec'} - )} -
-
+
+ + {canWritePolicyManagement ? ( + + ) : ( + + {viewModeSwitchLabel} + + )} + +
- - -
- {renderManifestOutdatedCallOut()} - {renderContent()} -
- + + +
+ {renderManifestOutdatedCallOut()} + {renderContent()} +
+ + + ); } ); diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx index 10d963100f89c..047b490fcb137 100644 --- a/x-pack/plugins/serverless_search/public/layout/nav.tsx +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -16,14 +16,6 @@ import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; -// Hiding this until page is in a better space -const _connectorItem = { - link: 'serverlessConnectors', - title: i18n.translate('xpack.serverlessSearch.nav.connectors', { - defaultMessage: 'Connectors', - }), -}; - const navigationTree: NavigationTreeDefinition = { body: [ { type: 'recentlyAccessed' }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 1e8ea9f239326..3a0bdfd7514c1 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -3508,10 +3508,7 @@ "home.sampleData.logsSpecDescription": "Exemple de données, de visualisations et de tableaux de bord pour le monitoring des logs Internet.", "home.sampleData.logsSpecTitle": "Exemple de logs Internet", "home.sampleData.logsTsdbSpec.bytesDistributionTitle": "[Logs TSDB] Distribution des octets", - "home.sampleData.logsTsdbSpec.goalsTitle": "[Logs TSDB] Objectifs", "home.sampleData.logsTsdbSpec.heatmapTitle": "[Logs TSDB] Carte thermique de destination unique", - "home.sampleData.logsTsdbSpec.hostVisitsBytesTableTitle": "[Logs TSDB] Tableau des hôtes, visites et octets", - "home.sampleData.logsTsdbSpec.responseCodesOverTimeTitle": "[Logs TSDB] Codes de réponse sur la durée + annotations", "home.sampleData.logsTsdbSpec.sourceAndDestinationSankeyChartTitle": "[Logs TSDB] Diagramme de Sankey de système d'exploitation source et destination", "home.sampleData.logsTsdbSpec.visitorsMapTitle": "[Logs TSDB] Carte des visiteurs", "home.sampleData.logsTsdbSpec.webTrafficDescription": "Analyser des données de log factices relatives au trafic Internet du site d'Elastic", @@ -38513,7 +38510,6 @@ "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.editButtonTooltip": "Modifier", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalIconAriaLabel": "Au-dessous de l'intervalle minimal configuré", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalTooltipTitle": "Au-dessous de l'intervalle minimal configuré", - "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastExecutionDateTitle": "Heure de début de la dernière exécution.", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastResponseTitle": "Dernière réponse", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.nameTitle": "Nom", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.notifyTitle": "Notifier", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 73d492e7031d1..3737127a10144 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3523,10 +3523,7 @@ "home.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", "home.sampleData.logsSpecTitle": "サンプル Web ログ", "home.sampleData.logsTsdbSpec.bytesDistributionTitle": "[Logs TSDB] バイト分布", - "home.sampleData.logsTsdbSpec.goalsTitle": "[Logs TSDB] 目標", "home.sampleData.logsTsdbSpec.heatmapTitle": "[Logs TSDB] 一意のターゲットヒートマップ", - "home.sampleData.logsTsdbSpec.hostVisitsBytesTableTitle": "[Logs TSDB] ホスト、訪問数、バイト表", - "home.sampleData.logsTsdbSpec.responseCodesOverTimeTitle": "[Logs TSDB] 一定期間の応答コードと注釈", "home.sampleData.logsTsdbSpec.sourceAndDestinationSankeyChartTitle": "[Logs TSDB] コンピューターOSとターゲットサンキーダイアグラム", "home.sampleData.logsTsdbSpec.visitorsMapTitle": "[Logs TSDB] 訪問者マップ", "home.sampleData.logsTsdbSpec.webTrafficDescription": "Elastic Web サイトのサンプル Webトラフィックログデータを分析します", @@ -38504,7 +38501,6 @@ "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.editButtonTooltip": "編集", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalIconAriaLabel": "構成された最小間隔未満", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalTooltipTitle": "構成された最小間隔未満", - "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastExecutionDateTitle": "前回の実行の開始時間。", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastResponseTitle": "前回の応答", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.nameTitle": "名前", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.notifyTitle": "通知", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3a87fc2238ee0..f7b67ac2f2661 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3522,10 +3522,7 @@ "home.sampleData.logsSpecDescription": "用于监测 Web 日志的样例数据、可视化和仪表板。", "home.sampleData.logsSpecTitle": "样例 Web 日志", "home.sampleData.logsTsdbSpec.bytesDistributionTitle": "[日志 TSDB] 字节分布", - "home.sampleData.logsTsdbSpec.goalsTitle": "[日志 TSDB] 目标", "home.sampleData.logsTsdbSpec.heatmapTitle": "[日志 TSDB] 唯一目标热图", - "home.sampleData.logsTsdbSpec.hostVisitsBytesTableTitle": "[日志 TSDB] 主机、访问和字节表", - "home.sampleData.logsTsdbSpec.responseCodesOverTimeTitle": "[日志 TSDB] 时移响应代码 + 注释", "home.sampleData.logsTsdbSpec.sourceAndDestinationSankeyChartTitle": "[日志 TSDB] 机器 OS 和目标 Sankey 图", "home.sampleData.logsTsdbSpec.visitorsMapTitle": "[日志 TSDB] 访客地图", "home.sampleData.logsTsdbSpec.webTrafficDescription": "分析 Elastic 网站的模拟 Web 流量日志数据", @@ -38498,7 +38495,6 @@ "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.editButtonTooltip": "编辑", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalIconAriaLabel": "低于配置的最小时间间隔", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.intervalTooltipTitle": "低于配置的最小时间间隔", - "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastExecutionDateTitle": "上次运行的开始时间。", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastResponseTitle": "上次响应", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.nameTitle": "名称", "xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.notifyTitle": "通知", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_auto_refresh.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_auto_refresh.tsx index eea8d8e5f1bbe..42c59667e0e39 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_auto_refresh.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_auto_refresh.tsx @@ -29,7 +29,7 @@ const getLastUpdateText = (lastUpdate: string) => { { defaultMessage: 'Updated {lastUpdateText}', values: { - lastUpdateText: moment(lastUpdate).fromNow(), + lastUpdateText: moment(lastUpdate).locale(i18n.getLocale()).fromNow(), }, } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx index 34c1f83878ef6..3d929cf7bb5b8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx @@ -413,14 +413,17 @@ export const RulesListTable = (props: RulesListTableProps) => { - Last run{' '} + {i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastRunTitle', + { defaultMessage: 'Last run' } + )} @@ -569,14 +572,17 @@ export const RulesListTable = (props: RulesListTableProps) => { - Duration{' '} + {i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitle', + { defaultMessage: 'Duration' } + )} @@ -639,14 +645,17 @@ export const RulesListTable = (props: RulesListTableProps) => { - Success ratio{' '} + {i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.successRatioTitle', + { defaultMessage: 'Success ratio' } + )} diff --git a/yarn.lock b/yarn.lock index ac82dfdfe47dd..7e9bb35da1443 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4483,6 +4483,10 @@ version "0.0.0" uid "" +"@kbn/feature-controls-examples-plugin@link:examples/feature_control_examples": + version "0.0.0" + uid "" + "@kbn/feature-usage-test-plugin@link:x-pack/test/plugin_api_integration/plugins/feature_usage_test": version "0.0.0" uid ""