From 5e7984650bfd3a784ec26e86a7bed6e03383340c Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 18 Sep 2023 12:27:13 +0200 Subject: [PATCH 01/58] [Bundling] Add redux (and inner deps) to shared bundle (#166367) ## Summary Add the `@redux/toolkit` and some related deps to the shared bundle. Full list of added libraries: * `@reduxjs/toolkit` * `redux` * `react-redux` * `immer` * `reselect` ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- packages/kbn-ui-shared-deps-npm/BUILD.bazel | 5 +++++ packages/kbn-ui-shared-deps-npm/webpack.config.js | 5 +++++ packages/kbn-ui-shared-deps-src/src/definitions.js | 5 +++++ packages/kbn-ui-shared-deps-src/src/entry.js | 5 +++++ .../edit_on_the_fly/get_edit_lens_configuration.tsx | 2 +- .../lens/public/state_management/fullscreen_middleware.ts | 2 +- x-pack/plugins/lens/public/state_management/index.ts | 8 ++++---- 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index 61569ac39c41d..f2ff64942a993 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -62,6 +62,11 @@ RUNTIME_DEPS = [ "@npm//tslib", "@npm//uuid", "@npm//io-ts", + "@npm//@reduxjs/toolkit", + "@npm//redux", + "@npm//react-redux", + "@npm//immer", + "@npm//reselect" ] webpack_cli( diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 21eb15d016f7b..a7beaf4462dc8 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -84,6 +84,10 @@ module.exports = (_, argv) => { '@emotion/cache', '@emotion/react', '@hello-pangea/dnd/dist/dnd.js', + '@reduxjs/toolkit', + 'redux', + 'react-redux', + 'immer', '@tanstack/react-query', '@tanstack/react-query-devtools', 'classnames', @@ -103,6 +107,7 @@ module.exports = (_, argv) => { 'react-router-dom-v5-compat', 'react-router', 'react', + 'reselect', 'rxjs', 'rxjs/operators', 'styled-components', diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 08e5355a3f444..ae9dcd3b056f1 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -56,6 +56,11 @@ const externals = { // this is how plugins/consumers from npm load monaco 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBarePluginApi', 'io-ts': '__kbnSharedDeps__.IoTs', + '@reduxjs/toolkit': '__kbnSharedDeps__.ReduxjsToolkit', + 'react-redux': '__kbnSharedDeps__.ReactRedux', + redux: '__kbnSharedDeps__.Redux', + immer: '__kbnSharedDeps__.Immer', + reselect: '__kbnSharedDeps__.Reselect', /** * big deps which are locked to a single version diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index ac203abadb39a..2491a34193e2e 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -47,6 +47,11 @@ export const ElasticEuiLibServicesFormat = require('@elastic/eui/optimize/es/ser export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); export const KbnDatemath = require('@kbn/datemath'); export const HelloPangeaDnd = require('@hello-pangea/dnd/dist/dnd'); +export const ReduxjsToolkit = require('@reduxjs/toolkit'); +export const ReactRedux = require('react-redux'); +export const Redux = require('redux'); +export const Immer = require('immer'); +export const Reselect = require('reselect'); export const Lodash = require('lodash'); export const LodashFp = require('lodash/fp'); diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx index b0e3ec119c532..18213c0bec4c0 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiFlyout, EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Provider } from 'react-redux'; -import { MiddlewareAPI, Dispatch, Action } from '@reduxjs/toolkit'; +import type { MiddlewareAPI, Dispatch, Action } from '@reduxjs/toolkit'; import { css } from '@emotion/react'; import type { CoreStart } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts b/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts index 5bbcdbf5b15d9..e800d4fed2d1b 100644 --- a/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts +++ b/x-pack/plugins/lens/public/state_management/fullscreen_middleware.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Dispatch, MiddlewareAPI, Action } from '@reduxjs/toolkit'; +import type { Dispatch, MiddlewareAPI, Action } from '@reduxjs/toolkit'; import { LensGetState, LensStoreDeps } from '.'; import { setToggleFullscreen } from './lens_slice'; diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index b426183eebaf2..f2770f2fd2caf 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -8,10 +8,10 @@ import { configureStore, getDefaultMiddleware, - PreloadedState, - Action, - Dispatch, - MiddlewareAPI, + type PreloadedState, + type Action, + type Dispatch, + type MiddlewareAPI, } from '@reduxjs/toolkit'; import { createLogger } from 'redux-logger'; import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; From b2057ac148a0f87bff07e5ad6f56e95a46ce1269 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:44:06 +0200 Subject: [PATCH 02/58] [Fleet] added agents per output telemetry (#166432) ## Summary Resolves https://github.com/elastic/ingest-dev/issues/1729 Added new telemetry about which output types agents use. Since agents can have different data and monitoring outputs, added 2 different counts. If an agent uses the same output as data and monitoring, it shows up in both counts. ``` [ { output_type: 'elasticsearch', count_as_data: 3, count_as_monitoring: 3 }, { output_type: 'logstash', count_as_data: 1, count_as_monitoring: 0 }, { output_type: 'kafka', count_as_data: 0, count_as_monitoring: 1 }, ] ``` To verify: start kibana locally and wait for the FleetUsageSender task to fire (every hour), it should show up in the debug logs. To speed it up, change the interval locally to a few minutes [here](https://github.com/elastic/kibana/pull/166432/files#diff-fca0b4eb6c08f4b21ad3c69bd1b9376d4665083a49095f1b621f6d86cd091674). ``` [2023-09-14T11:37:49.358+02:00][DEBUG][plugins.fleet] Agents per output type telemetry: [{"output_type":"elasticsearch","count_as_data":32,"count_as_monitoring":32}] ``` Telemetry mappings merged to staging [here](https://github.com/elastic/telemetry/pull/2602/), should update prod version when verified. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../collectors/agents_per_output.test.ts | 51 ++++++++++++ .../server/collectors/agents_per_output.ts | 70 +++++++++++++++++ .../fleet/server/collectors/register.ts | 4 + .../fleet_usage_telemetry.test.ts | 78 +++++++++++++------ .../services/telemetry/fleet_usage_sender.ts | 17 +++- .../services/telemetry/fleet_usages_schema.ts | 30 +++++++ 6 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts create mode 100644 x-pack/plugins/fleet/server/collectors/agents_per_output.ts diff --git a/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts b/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts new file mode 100644 index 0000000000000..3e91282891f2f --- /dev/null +++ b/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts @@ -0,0 +1,51 @@ +/* + * 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 { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; + +import { getAgentsPerOutput } from './agents_per_output'; + +jest.mock('../services', () => { + return { + agentPolicyService: { + list: jest.fn().mockResolvedValue({ + items: [ + { agents: 0, data_output_id: 'logstash1', monitoring_output_id: 'kafka1' }, + { agents: 1 }, + { agents: 1, data_output_id: 'logstash1' }, + { agents: 1, monitoring_output_id: 'kafka1' }, + { agents: 1, data_output_id: 'elasticsearch2', monitoring_output_id: 'elasticsearch2' }, + ], + }), + }, + }; +}); + +describe('agents_per_output', () => { + const soClientMock = { + find: jest.fn().mockResolvedValue({ + saved_objects: [ + { + id: 'default-output', + attributes: { is_default: true, is_default_monitoring: true, type: 'elasticsearch' }, + }, + { id: 'logstash1', attributes: { type: 'logstash' } }, + { id: 'kafka1', attributes: { type: 'kafka' } }, + { id: 'elasticsearch2', attributes: { type: 'elasticsearch' } }, + ], + }), + } as unknown as SavedObjectsClientContract; + + it('should return agent count by output type', async () => { + const res = await getAgentsPerOutput(soClientMock, {} as unknown as ElasticsearchClient); + expect(res).toEqual([ + { output_type: 'elasticsearch', count_as_data: 3, count_as_monitoring: 3 }, + { output_type: 'logstash', count_as_data: 1, count_as_monitoring: 0 }, + { output_type: 'kafka', count_as_data: 0, count_as_monitoring: 1 }, + ]); + }); +}); diff --git a/x-pack/plugins/fleet/server/collectors/agents_per_output.ts b/x-pack/plugins/fleet/server/collectors/agents_per_output.ts new file mode 100644 index 0000000000000..54733dace2057 --- /dev/null +++ b/x-pack/plugins/fleet/server/collectors/agents_per_output.ts @@ -0,0 +1,70 @@ +/* + * 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 { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import _ from 'lodash'; + +import { OUTPUT_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../common'; +import type { OutputSOAttributes } from '../types'; +import { agentPolicyService } from '../services'; + +export interface AgentsPerOutputType { + output_type: string; + count_as_data: number; + count_as_monitoring: number; +} + +export async function getAgentsPerOutput( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient +): Promise { + const { saved_objects: outputs } = await soClient.find({ + type: OUTPUT_SAVED_OBJECT_TYPE, + page: 1, + perPage: SO_SEARCH_LIMIT, + }); + + const defaultOutputId = outputs.find((output) => output.attributes.is_default)?.id || ''; + const defaultMonitoringOutputId = + outputs.find((output) => output.attributes.is_default_monitoring)?.id || ''; + + const outputsById = _.keyBy(outputs, 'id'); + const getOutputTypeById = (outputId: string): string => outputsById[outputId]?.attributes.type; + + const { items } = await agentPolicyService.list(soClient, { + esClient, + withAgentCount: true, + page: 1, + perPage: SO_SEARCH_LIMIT, + }); + const outputTypes: { [key: string]: AgentsPerOutputType } = {}; + items + .filter((item) => (item.agents ?? 0) > 0) + .forEach((item) => { + const dataOutputType = getOutputTypeById(item.data_output_id || defaultOutputId); + if (!outputTypes[dataOutputType]) { + outputTypes[dataOutputType] = { + output_type: dataOutputType, + count_as_data: 0, + count_as_monitoring: 0, + }; + } + outputTypes[dataOutputType].count_as_data += item.agents ?? 0; + const monitoringOutputType = getOutputTypeById( + item.monitoring_output_id || defaultMonitoringOutputId + ); + if (!outputTypes[monitoringOutputType]) { + outputTypes[monitoringOutputType] = { + output_type: monitoringOutputType, + count_as_data: 0, + count_as_monitoring: 0, + }; + } + outputTypes[monitoringOutputType].count_as_monitoring += item.agents ?? 0; + }); + return Object.values(outputTypes); +} diff --git a/x-pack/plugins/fleet/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts index d31548d330897..01d0a65f2cf08 100644 --- a/x-pack/plugins/fleet/server/collectors/register.ts +++ b/x-pack/plugins/fleet/server/collectors/register.ts @@ -22,6 +22,8 @@ import { getAgentPoliciesUsage } from './agent_policies'; import type { AgentPanicLogsData } from './agent_logs_panics'; import { getPanicLogsLastHour } from './agent_logs_panics'; import { getAgentLogsTopErrors } from './agent_logs_top_errors'; +import type { AgentsPerOutputType } from './agents_per_output'; +import { getAgentsPerOutput } from './agents_per_output'; export interface Usage { agents_enabled: boolean; @@ -36,6 +38,7 @@ export interface FleetUsage extends Usage, AgentData { agent_logs_panics_last_hour: AgentPanicLogsData['agent_logs_panics_last_hour']; agent_logs_top_errors?: string[]; fleet_server_logs_top_errors?: string[]; + agents_per_output_type: AgentsPerOutputType[]; } export const fetchFleetUsage = async ( @@ -57,6 +60,7 @@ export const fetchFleetUsage = async ( agent_policies: await getAgentPoliciesUsage(soClient), ...(await getPanicLogsLastHour(esClient)), ...(await getAgentLogsTopErrors(esClient)), + agents_per_output_type: await getAgentsPerOutput(soClient, esClient), }; return usage; }; diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts index 2aeb7a0e1a963..046ac5dfe9fad 100644 --- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts @@ -207,6 +207,20 @@ describe('fleet usage telemetry', () => { }, ], }, + { + create: { + _id: 'agent3', + }, + }, + { + agent: { + version: '8.6.0', + }, + last_checkin_status: 'online', + last_checkin: '2023-09-13T12:26:24Z', + active: true, + policy_id: 'policy2', + }, ], refresh: 'wait_for', }); @@ -348,20 +362,24 @@ describe('fleet usage telemetry', () => { { id: 'output3' } ); - await soClient.create('ingest-agent-policies', { - namespace: 'default', - monitoring_enabled: ['logs', 'metrics'], - name: 'Another policy', - description: 'Policy 2', - inactivity_timeout: 1209600, - status: 'active', - is_managed: false, - revision: 2, - updated_by: 'system', - schema_version: '1.0.0', - data_output_id: 'output2', - monitoring_output_id: 'output3', - }); + await soClient.create( + 'ingest-agent-policies', + { + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + name: 'Another policy', + description: 'Policy 2', + inactivity_timeout: 1209600, + status: 'active', + is_managed: false, + revision: 2, + updated_by: 'system', + schema_version: '1.0.0', + data_output_id: 'output2', + monitoring_output_id: 'output3', + }, + { id: 'policy2' } + ); }); afterAll(async () => { @@ -379,13 +397,13 @@ describe('fleet usage telemetry', () => { expect.objectContaining({ agents_enabled: true, agents: { - total_enrolled: 2, + total_enrolled: 3, healthy: 0, unhealthy: 0, inactive: 0, unenrolled: 1, - offline: 2, - total_all_statuses: 3, + offline: 3, + total_all_statuses: 4, updating: 0, }, fleet_server: { @@ -400,28 +418,28 @@ describe('fleet usage telemetry', () => { packages: [], agents_per_version: [ { - version: '8.5.1', - count: 1, + version: '8.6.0', + count: 2, healthy: 0, inactive: 0, - offline: 1, - unenrolled: 1, + offline: 2, + unenrolled: 0, unhealthy: 0, updating: 0, }, { - version: '8.6.0', + version: '8.5.1', count: 1, healthy: 0, inactive: 0, offline: 1, - unenrolled: 0, + unenrolled: 1, unhealthy: 0, updating: 0, }, ], agent_checkin_status: { error: 1, degraded: 1 }, - agents_per_policy: [2], + agents_per_policy: [2, 1], agents_per_os: [ { name: 'Ubuntu', @@ -434,6 +452,18 @@ describe('fleet usage telemetry', () => { count: 1, }, ], + agents_per_output_type: [ + { + count_as_data: 1, + count_as_monitoring: 0, + output_type: 'third_type', + }, + { + count_as_data: 0, + count_as_monitoring: 1, + output_type: 'logstash', + }, + ], fleet_server_config: { policies: [ { diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts index 102f34c280800..555223c7b5b1c 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts @@ -24,7 +24,7 @@ const FLEET_AGENTS_EVENT_TYPE = 'fleet_agents'; export class FleetUsageSender { private taskManager?: TaskManagerStartContract; - private taskVersion = '1.1.1'; + private taskVersion = '1.1.2'; private taskType = 'Fleet-Usage-Sender'; private wasStarted: boolean = false; private interval = '1h'; @@ -80,7 +80,11 @@ export class FleetUsageSender { if (!usageData) { return; } - const { agents_per_version: agentsPerVersion, ...fleetUsageData } = usageData; + const { + agents_per_version: agentsPerVersion, + agents_per_output_type: agentsPerOutputType, + ...fleetUsageData + } = usageData; appContextService .getLogger() .debug('Fleet usage telemetry: ' + JSON.stringify(fleetUsageData)); @@ -93,6 +97,15 @@ export class FleetUsageSender { agentsPerVersion.forEach((byVersion) => { core.analytics.reportEvent(FLEET_AGENTS_EVENT_TYPE, { agents_per_version: byVersion }); }); + + appContextService + .getLogger() + .debug('Agents per output type telemetry: ' + JSON.stringify(agentsPerOutputType)); + agentsPerOutputType.forEach((byOutputType) => { + core.analytics.reportEvent(FLEET_AGENTS_EVENT_TYPE, { + agents_per_output_type: byOutputType, + }); + }); } catch (error) { appContextService .getLogger() diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts index 616d30ad3d4b1..e59de684264bf 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts @@ -9,6 +9,10 @@ import type { RootSchema } from '@kbn/analytics-client'; export const fleetAgentsSchema: RootSchema = { agents_per_version: { + _meta: { + description: 'Agents per version telemetry', + optional: true, + }, properties: { version: { type: 'keyword', @@ -60,6 +64,32 @@ export const fleetAgentsSchema: RootSchema = { }, }, }, + agents_per_output_type: { + _meta: { + description: 'Agents per output type telemetry', + optional: true, + }, + properties: { + output_type: { + type: 'keyword', + _meta: { + description: 'Output type used by agent', + }, + }, + count_as_data: { + type: 'long', + _meta: { + description: 'Number of agents enrolled that use this output type as data output', + }, + }, + count_as_monitoring: { + type: 'long', + _meta: { + description: 'Number of agents enrolled that use this output type as monitoring output', + }, + }, + }, + }, }; export const fleetUsagesSchema: RootSchema = { From 1dee16e06169a756a3ed8856435ed572fddd20cf Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Mon, 18 Sep 2023 12:45:14 +0200 Subject: [PATCH 03/58] [Security Solution] Fix AI Assistant overlay in Serverless pages (#166437) ## Summary issue: https://github.com/elastic/kibana/issues/166436 ![AI Assistant header link](https://github.com/elastic/kibana/assets/17747913/66614838-0e70-45b3-b79e-f4e70253fba2) ### Bug The bug was caused by the `AssistantOverlay` being rendered inside the `SecuritySolutionPageWrapper`, which is a "styling" wrapper that is not used by all the pages in the Security Solution application, the serverless-specific pages for instance, use their own page wrapper for styling. ### Changes #### Fix This PR fixes the bug by rendering the `AssistantOverlay` in the `HomePage`, along with other similar components such as the `HelpMenu` or the `TopValuesPopover`, which are components that need to be always present in the application layout. #### Cleaning The assistant provider configuration logic has also been moved from the `App` component to a new `provider.tsx` component in the _public/assistant_ directory, this way we don't pollute the main Security `App` component with assistant-specific logic. The `isAssistantEnabled` prop has been removed from the `Assistant` component. We don't need to check the availability and pass it by props everywhere we use the assistant, it can be retrieved from the assistant context. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../assistant_overlay/index.test.tsx | 10 +-- .../assistant/assistant_overlay/index.tsx | 11 +-- .../impl/assistant/index.test.tsx | 33 +++++--- .../impl/assistant/index.tsx | 3 +- .../mock/test_providers/test_providers.tsx | 2 +- .../packages/kbn-elastic-assistant/index.ts | 2 +- .../security_solution/public/app/app.tsx | 58 +------------- .../app/home/global_header/index.test.tsx | 40 +--------- .../public/app/home/global_header/index.tsx | 7 +- .../public/app/home/index.tsx | 2 + .../public/app/translations.ts | 4 - .../public/assistant/header_link.test.tsx | 47 +++++++++++ .../header_link.tsx} | 41 +++++----- .../public/assistant/overlay.test.tsx | 37 +++++++++ .../public/assistant/overlay.tsx | 19 +++++ .../public/assistant/provider.tsx | 77 +++++++++++++++++++ .../common/components/page_wrapper/index.tsx | 4 - .../timeline/tabs_content/index.tsx | 42 +++------- 18 files changed, 253 insertions(+), 186 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/assistant/header_link.test.tsx rename x-pack/plugins/security_solution/public/{app/home/global_header/assistant_header_link.tsx => assistant/header_link.tsx} (61%) create mode 100644 x-pack/plugins/security_solution/public/assistant/overlay.test.tsx create mode 100644 x-pack/plugins/security_solution/public/assistant/overlay.tsx create mode 100644 x-pack/plugins/security_solution/public/assistant/provider.tsx diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx index ff96f8b66c92b..972d3d9099cd0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.test.tsx @@ -23,7 +23,7 @@ describe('AssistantOverlay', () => { it('renders when isAssistantEnabled prop is true and keyboard shortcut is pressed', () => { const { getByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -34,7 +34,7 @@ describe('AssistantOverlay', () => { it('modal closes when close button is clicked', () => { const { getByLabelText, queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -47,7 +47,7 @@ describe('AssistantOverlay', () => { it('Assistant invoked from shortcut tracking happens on modal open only (not close)', () => { render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -63,7 +63,7 @@ describe('AssistantOverlay', () => { it('modal closes when shortcut is pressed and modal is already open', () => { const { queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: ';', ctrlKey: true }); @@ -75,7 +75,7 @@ describe('AssistantOverlay', () => { it('modal does not open when incorrect shortcut is pressed', () => { const { queryByTestId } = render( - + ); fireEvent.keyDown(document, { key: 'a', ctrlKey: true }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx index 43635fba95df5..ac72fc27dd891 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx @@ -22,15 +22,12 @@ const StyledEuiModal = styled(EuiModal)` min-width: 95vw; min-height: 25vh; `; -interface Props { - isAssistantEnabled: boolean; -} /** * Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever * component currently has focus and any specific context it may provide through the SAssInterface. */ -export const AssistantOverlay = React.memo(({ isAssistantEnabled }) => { +export const AssistantOverlay = React.memo(() => { const [isModalVisible, setIsModalVisible] = useState(false); const [conversationId, setConversationId] = useState( WELCOME_CONVERSATION_TITLE @@ -103,11 +100,7 @@ export const AssistantOverlay = React.memo(({ isAssistantEnabled }) => { <> {isModalVisible && ( - + )} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx index fcd203ce0cd97..acb41a9575581 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.test.tsx @@ -22,7 +22,7 @@ import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations'; import { useLocalStorage } from 'react-use'; import { PromptEditor } from './prompt_editor'; import { QuickPrompts } from './quick_prompts/quick_prompts'; -import { TestProviders } from '../mock/test_providers/test_providers'; +import { mockAssistantAvailability, TestProviders } from '../mock/test_providers/test_providers'; jest.mock('../connectorland/use_load_connectors'); jest.mock('../connectorland/connector_setup'); @@ -46,10 +46,10 @@ const getInitialConversations = (): Record => ({ }, }); -const renderAssistant = (extraProps = {}) => +const renderAssistant = (extraProps = {}, providerProps = {}) => render( - - + + ); @@ -110,9 +110,10 @@ describe('Assistant', () => { data: connectors, } as unknown as UseQueryResult); - const { getByLabelText } = render( - ({ + const { getByLabelText } = renderAssistant( + {}, + { + getInitialConversations: () => ({ [WELCOME_CONVERSATION_TITLE]: { id: WELCOME_CONVERSATION_TITLE, messages: [], @@ -124,10 +125,8 @@ describe('Assistant', () => { apiConfig: {}, excludeFromLastConversationStorage: true, }, - })} - > - - + }), + } ); expect(persistToLocalStorage).toHaveBeenCalled(); @@ -176,4 +175,16 @@ describe('Assistant', () => { expect(persistToLocalStorage).toHaveBeenLastCalledWith(WELCOME_CONVERSATION_TITLE); }); }); + + describe('when not authorized', () => { + it('should be disabled', async () => { + const { queryByTestId } = renderAssistant( + {}, + { + assistantAvailability: { ...mockAssistantAvailability, isAssistantEnabled: false }, + } + ); + expect(queryByTestId('prompt-textarea')).toHaveProperty('disabled'); + }); + }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index 4ea1ed240870d..d119526a198c5 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -51,7 +51,6 @@ import { ConnectorMissingCallout } from '../connectorland/connector_missing_call export interface Props { conversationId?: string; - isAssistantEnabled: boolean; promptContextId?: string; shouldRefocusPrompt?: boolean; showTitle?: boolean; @@ -64,7 +63,6 @@ export interface Props { */ const AssistantComponent: React.FC = ({ conversationId, - isAssistantEnabled, promptContextId = '', shouldRefocusPrompt = false, showTitle = true, @@ -73,6 +71,7 @@ const AssistantComponent: React.FC = ({ const { assistantTelemetry, augmentMessageCodeBlocks, + assistantAvailability: { isAssistantEnabled }, conversations, defaultAllow, defaultAllowReplacement, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx index 484dd316cc0ac..057db39f66ba2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/mock/test_providers/test_providers.tsx @@ -29,7 +29,7 @@ window.HTMLElement.prototype.scrollIntoView = jest.fn(); const mockGetInitialConversations = () => ({}); -const mockAssistantAvailability: AssistantAvailability = { +export const mockAssistantAvailability: AssistantAvailability = { hasAssistantPrivilege: false, hasConnectorsAllPrivilege: true, hasConnectorsReadPrivilege: true, diff --git a/x-pack/packages/kbn-elastic-assistant/index.ts b/x-pack/packages/kbn-elastic-assistant/index.ts index 1353db4908006..8ec14b143ba13 100644 --- a/x-pack/packages/kbn-elastic-assistant/index.ts +++ b/x-pack/packages/kbn-elastic-assistant/index.ts @@ -11,7 +11,7 @@ // happens in the root of your app. Optionally provide a custom title for the assistant: /** provides context (from the app) to the assistant, and injects Kibana services, like `http` */ -export { AssistantProvider } from './impl/assistant_context'; +export { AssistantProvider, useAssistantContext } from './impl/assistant_context'; // Step 2: Add the `AssistantOverlay` component to your app. This component displays the assistant // overlay in a modal, bound to a shortcut key: diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 8bbf2c3a425ec..88ced06445c07 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import { AssistantProvider } from '@kbn/elastic-assistant'; import type { History } from 'history'; import type { FC } from 'react'; -import React, { memo, useCallback } from 'react'; +import React, { memo } from 'react'; import type { Store, Action } from 'redux'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -21,28 +20,18 @@ import { CellActionsProvider } from '@kbn/cell-actions'; import { NavigationProvider } from '@kbn/security-solution-navigation'; import { UpsellingProvider } from '../common/components/upselling_provider'; -import { useAssistantTelemetry } from '../assistant/use_assistant_telemetry'; -import { getComments } from '../assistant/get_comments'; -import { augmentMessageCodeBlocks, LOCAL_STORAGE_KEY } from '../assistant/helpers'; -import { useConversationStore } from '../assistant/use_conversation_store'; import { ManageUserInfo } from '../detections/components/user_info'; -import { DEFAULT_DARK_MODE, APP_NAME, APP_ID } from '../../common/constants'; +import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants'; import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher'; import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_capabilities_provider'; import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters'; import { KibanaContextProvider, useKibana, useUiSetting$ } from '../common/lib/kibana'; import type { State } from '../common/store'; -import { ASSISTANT_TITLE } from './translations'; import type { StartServices } from '../types'; import { PageRouter } from './routes'; import { UserPrivilegesProvider } from '../common/components/user_privileges/user_privileges_context'; import { ReactQueryClientProvider } from '../common/containers/query_client/query_client_provider'; -import { DEFAULT_ALLOW, DEFAULT_ALLOW_REPLACEMENT } from '../assistant/content/anonymization'; -import { PROMPT_CONTEXTS } from '../assistant/content/prompt_contexts'; -import { BASE_SECURITY_QUICK_PROMPTS } from '../assistant/content/quick_prompts'; -import { BASE_SECURITY_SYSTEM_PROMPTS } from '../assistant/content/prompts/system'; -import { useAnonymizationStore } from '../assistant/use_anonymization_store'; -import { useAssistantAvailability } from '../assistant/use_assistant_availability'; +import { AssistantProvider } from '../assistant/provider'; interface StartAppComponent { children: React.ReactNode; @@ -65,29 +54,12 @@ const StartAppComponent: FC = ({ const { i18n, application: { capabilities }, - http, - triggersActionsUi: { actionTypeRegistry }, uiActions, upselling, } = services; - const assistantAvailability = useAssistantAvailability(); - const { conversations, setConversations } = useConversationStore(); - const { defaultAllow, defaultAllowReplacement, setDefaultAllow, setDefaultAllowReplacement } = - useAnonymizationStore(); - - const getInitialConversation = useCallback(() => { - return conversations; - }, [conversations]); - - const nameSpace = `${APP_ID}.${LOCAL_STORAGE_KEY}`; - const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = useKibana().services.docLinks; - - const assistantTelemetry = useAssistantTelemetry(); - return ( @@ -95,29 +67,7 @@ const StartAppComponent: FC = ({ - + diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx index 7dc0b338193a9..fe0b5bd500dc8 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.test.tsx @@ -26,7 +26,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { createStore } from '../../../common/store'; import { kibanaObservable } from '@kbn/timelines-plugin/public/mock'; import { sourcererPaths } from '../../../common/containers/sourcerer'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -49,15 +48,6 @@ jest.mock('react-reverse-portal', () => ({ createHtmlPortalNode: () => ({ unmount: jest.fn() }), })); -jest.mock('../../../assistant/use_assistant_availability'); - -jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: false, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, -}); - describe('global header', () => { const mockSetHeaderActionMenu = jest.fn(); const state = { @@ -183,18 +173,11 @@ describe('global header', () => { expect(queryByTestId('sourcerer-trigger')).not.toBeInTheDocument(); }); - it('shows AI Assistant header link if user has necessary privileges', () => { + it('shows AI Assistant header link', () => { (useLocation as jest.Mock).mockReturnValue([ { pageName: SecurityPageName.overview, detailName: undefined }, ]); - jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: true, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, - }); - const { findByTestId } = render( @@ -203,25 +186,4 @@ describe('global header', () => { waitFor(() => expect(findByTestId('assistantHeaderLink')).toBeInTheDocument()); }); - - it('does not show AI Assistant header link if user does not have necessary privileges', () => { - (useLocation as jest.Mock).mockReturnValue([ - { pageName: SecurityPageName.overview, detailName: undefined }, - ]); - - jest.mocked(useAssistantAvailability).mockReturnValue({ - hasAssistantPrivilege: false, - hasConnectorsAllPrivilege: true, - hasConnectorsReadPrivilege: true, - isAssistantEnabled: true, - }); - - const { findByTestId } = render( - - - - ); - - waitFor(() => expect(findByTestId('assistantHeaderLink')).not.toBeInTheDocument()); - }); }); diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx index 615d4e8161786..bde0b71a43270 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -27,8 +27,7 @@ import { timelineSelectors } from '../../../timelines/store/timeline'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { getScopeFromPath, showSourcererByPath } from '../../../common/containers/sourcerer'; import { useAddIntegrationsUrl } from '../../../common/hooks/use_add_integrations_url'; -import { AssistantHeaderLink } from './assistant_header_link'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; +import { AssistantHeaderLink } from '../../../assistant/header_link'; const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { defaultMessage: 'Add integrations', @@ -54,8 +53,6 @@ export const GlobalHeader = React.memo( const { href, onClick } = useAddIntegrationsUrl(); - const { hasAssistantPrivilege } = useAssistantAvailability(); - useEffect(() => { setHeaderActionMenu((element) => { const mount = toMountPoint(, { theme$: theme.theme$ }); @@ -91,7 +88,7 @@ export const GlobalHeader = React.memo( {showSourcerer && !showTimeline && ( )} - {hasAssistantPrivilege && } + diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 1e98b1c438957..b951501b16cb7 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -29,6 +29,7 @@ import { useUpdateExecutionContext } from '../../common/hooks/use_update_executi import { useUpgradeSecurityPackages } from '../../detection_engine/rule_management/logic/use_upgrade_security_packages'; import { useSetupDetectionEngineHealthApi } from '../../detection_engine/rule_monitoring'; import { TopValuesPopover } from '../components/top_values_popover/top_values_popover'; +import { AssistantOverlay } from '../../assistant/overlay'; interface HomePageProps { children: React.ReactNode; @@ -63,6 +64,7 @@ const HomePageComponent: React.FC = ({ children, setHeaderActionM + diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index fcee866a2da3a..516239d164632 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -7,10 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', { - defaultMessage: 'Elastic AI Assistant', -}); - export const OVERVIEW = i18n.translate('xpack.securitySolution.navigation.overview', { defaultMessage: 'Overview', }); diff --git a/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx b/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx new file mode 100644 index 0000000000000..e6b8cfea388a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/header_link.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { AssistantHeaderLink } from './header_link'; + +const mockShowAssistantOverlay = jest.fn(); +const mockAssistantAvailability = jest.fn(() => ({ + hasAssistantPrivilege: true, +})); +jest.mock('@kbn/elastic-assistant/impl/assistant_context', () => ({ + useAssistantContext: () => ({ + assistantAvailability: mockAssistantAvailability(), + showAssistantOverlay: mockShowAssistantOverlay, + }), +})); + +describe('AssistantHeaderLink', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header link text', () => { + const { queryByText, queryByTestId } = render(); + expect(queryByTestId('assistantHeaderLink')).toBeInTheDocument(); + expect(queryByText('AI Assistant')).toBeInTheDocument(); + }); + + it('should not render the header link if not authorized', () => { + mockAssistantAvailability.mockReturnValueOnce({ hasAssistantPrivilege: false }); + + const { queryByText, queryByTestId } = render(); + expect(queryByTestId('assistantHeaderLink')).not.toBeInTheDocument(); + expect(queryByText('AI Assistant')).not.toBeInTheDocument(); + }); + + it('should call the assistant overlay to show on click', () => { + const { queryByTestId } = render(); + queryByTestId('assistantHeaderLink')?.click(); + expect(mockShowAssistantOverlay).toHaveBeenCalledTimes(1); + expect(mockShowAssistantOverlay).toHaveBeenCalledWith({ showOverlay: true }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.tsx b/x-pack/plugins/security_solution/public/assistant/header_link.tsx similarity index 61% rename from x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.tsx rename to x-pack/plugins/security_solution/public/assistant/header_link.tsx index 00e1acf7e45d5..342a95454cdb4 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/assistant_header_link.tsx +++ b/x-pack/plugins/security_solution/public/assistant/header_link.tsx @@ -14,43 +14,42 @@ import { AssistantAvatar } from '@kbn/elastic-assistant'; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; +const TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.globalHeader.assistantHeaderLinkShortcutTooltip', + { + values: { keyboardShortcut: isMac ? '⌘ ;' : 'Ctrl ;' }, + defaultMessage: 'Keyboard shortcut {keyboardShortcut}', + } +); +const LINK_LABEL = i18n.translate('xpack.securitySolution.globalHeader.assistantHeaderLink', { + defaultMessage: 'AI Assistant', +}); + /** * Elastic AI Assistant header link */ -export const AssistantHeaderLink = React.memo(() => { - const { showAssistantOverlay } = useAssistantContext(); - - const keyboardShortcut = isMac ? '⌘ ;' : 'Ctrl ;'; - - const tooltipContent = i18n.translate( - 'xpack.securitySolution.globalHeader.assistantHeaderLinkShortcutTooltip', - { - values: { keyboardShortcut }, - defaultMessage: 'Keyboard shortcut {keyboardShortcut}', - } - ); +export const AssistantHeaderLink = () => { + const { showAssistantOverlay, assistantAvailability } = useAssistantContext(); const showOverlay = useCallback( () => showAssistantOverlay({ showOverlay: true }), [showAssistantOverlay] ); + if (!assistantAvailability.hasAssistantPrivilege) { + return null; + } + return ( - + - - {i18n.translate('xpack.securitySolution.globalHeader.assistantHeaderLink', { - defaultMessage: 'AI Assistant', - })} - + {LINK_LABEL} ); -}); - -AssistantHeaderLink.displayName = 'AssistantHeaderLink'; +}; diff --git a/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx b/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx new file mode 100644 index 0000000000000..fb082a4677595 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/overlay.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { AssistantOverlay } from './overlay'; + +const mockAssistantAvailability = jest.fn(() => ({ + hasAssistantPrivilege: true, +})); +jest.mock('@kbn/elastic-assistant', () => ({ + AssistantOverlay: () =>
, + useAssistantContext: () => ({ + assistantAvailability: mockAssistantAvailability(), + }), +})); + +describe('AssistantOverlay', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the header link text', () => { + const { queryByTestId } = render(); + expect(queryByTestId('assistantOverlay')).toBeInTheDocument(); + }); + + it('should not render the header link if not authorized', () => { + mockAssistantAvailability.mockReturnValueOnce({ hasAssistantPrivilege: false }); + + const { queryByTestId } = render(); + expect(queryByTestId('assistantOverlay')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/assistant/overlay.tsx b/x-pack/plugins/security_solution/public/assistant/overlay.tsx new file mode 100644 index 0000000000000..3ffaddaf2e8f5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/overlay.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { + AssistantOverlay as ElasticAssistantOverlay, + useAssistantContext, +} from '@kbn/elastic-assistant'; + +export const AssistantOverlay: React.FC = () => { + const { assistantAvailability } = useAssistantContext(); + if (!assistantAvailability.hasAssistantPrivilege) { + return null; + } + return ; +}; diff --git a/x-pack/plugins/security_solution/public/assistant/provider.tsx b/x-pack/plugins/security_solution/public/assistant/provider.tsx new file mode 100644 index 0000000000000..cde9d5b58524d --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/provider.tsx @@ -0,0 +1,77 @@ +/* + * 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 React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { AssistantProvider as ElasticAssistantProvider } from '@kbn/elastic-assistant'; +import { useKibana } from '../common/lib/kibana'; +import { useAssistantTelemetry } from './use_assistant_telemetry'; +import { getComments } from './get_comments'; +import { augmentMessageCodeBlocks, LOCAL_STORAGE_KEY } from './helpers'; +import { useConversationStore } from './use_conversation_store'; +import { DEFAULT_ALLOW, DEFAULT_ALLOW_REPLACEMENT } from './content/anonymization'; +import { PROMPT_CONTEXTS } from './content/prompt_contexts'; +import { BASE_SECURITY_QUICK_PROMPTS } from './content/quick_prompts'; +import { BASE_SECURITY_SYSTEM_PROMPTS } from './content/prompts/system'; +import { useAnonymizationStore } from './use_anonymization_store'; +import { useAssistantAvailability } from './use_assistant_availability'; +import { APP_ID } from '../../common/constants'; + +const ASSISTANT_TITLE = i18n.translate('xpack.securitySolution.assistant.title', { + defaultMessage: 'Elastic AI Assistant', +}); + +/** + * This component configures the Elastic AI Assistant context provider for the Security Solution app. + */ +export const AssistantProvider: React.FC = ({ children }) => { + const { + http, + triggersActionsUi: { actionTypeRegistry }, + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + } = useKibana().services; + + const { conversations, setConversations } = useConversationStore(); + const getInitialConversation = useCallback(() => { + return conversations; + }, [conversations]); + + const assistantAvailability = useAssistantAvailability(); + const assistantTelemetry = useAssistantTelemetry(); + + const { defaultAllow, defaultAllowReplacement, setDefaultAllow, setDefaultAllowReplacement } = + useAnonymizationStore(); + + const nameSpace = `${APP_ID}.${LOCAL_STORAGE_KEY}`; + + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx index d893674d49af5..acedbe669f36a 100644 --- a/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page_wrapper/index.tsx @@ -5,13 +5,11 @@ * 2.0. */ -import { AssistantOverlay } from '@kbn/elastic-assistant'; import classNames from 'classnames'; import React, { useEffect } from 'react'; import styled from 'styled-components'; import type { CommonProps } from '@elastic/eui'; -import { useAssistantAvailability } from '../../../assistant/use_assistant_availability'; import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { AppGlobalStyle } from '../page'; @@ -42,7 +40,6 @@ interface SecuritySolutionPageWrapperProps { const SecuritySolutionPageWrapperComponent: React.FC< SecuritySolutionPageWrapperProps & CommonProps > = ({ children, className, style, noPadding, noTimeline, ...otherProps }) => { - const { isAssistantEnabled, hasAssistantPrivilege } = useAssistantAvailability(); const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); useEffect(() => { setGlobalFullScreen(false); // exit full screen mode on page load @@ -59,7 +56,6 @@ const SecuritySolutionPageWrapperComponent: React.FC< {children} - {hasAssistantPrivilege && } ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 3d5b1a95d66da..2707bf7b04ebc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -100,33 +100,19 @@ interface BasicTimelineTab { } const AssistantTab: React.FC<{ - isAssistantEnabled: boolean; - renderCellValue: (props: CellValueElementProps) => React.ReactNode; - rowRenderers: RowRenderer[]; - timelineId: TimelineId; shouldRefocusPrompt: boolean; setConversationId: Dispatch>; -}> = memo( - ({ - isAssistantEnabled, - renderCellValue, - rowRenderers, - timelineId, - shouldRefocusPrompt, - setConversationId, - }) => ( - }> - - - - - ) -); +}> = memo(({ shouldRefocusPrompt, setConversationId }) => ( + }> + + + + +)); AssistantTab.displayName = 'AssistantTab'; @@ -147,7 +133,7 @@ const ActiveTimelineTab = memo( showTimeline, }) => { const isDiscoverInTimelineEnabled = useIsExperimentalFeatureEnabled('discoverInTimeline'); - const { hasAssistantPrivilege, isAssistantEnabled } = useAssistantAvailability(); + const { hasAssistantPrivilege } = useAssistantAvailability(); const getTab = useCallback( (tab: TimelineTabs) => { switch (tab) { @@ -235,10 +221,6 @@ const ActiveTimelineTab = memo( {(activeTimelineTab === TimelineTabs.securityAssistant || hasTimelineConversationStarted) && ( Date: Mon, 18 Sep 2023 12:47:00 +0200 Subject: [PATCH 04/58] [Synthetics] Fix params sync is broken in non default space (#166557) --- .../synthetics/server/synthetics_service/synthetics_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index eae3fb0082030..561a0e5a90e4c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -608,7 +608,7 @@ export class SyntheticsService { await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser({ type: syntheticsParamType, perPage: 1000, - namespaces: spaceId ? [spaceId] : undefined, + namespaces: spaceId ? [spaceId] : [ALL_SPACES_ID], }); for await (const response of finder.find()) { From e00aa75631961fa7bbe061291472d3a63ab19502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Mon, 18 Sep 2023 12:59:12 +0200 Subject: [PATCH 05/58] [Security Solution][Defend Workflows] Fix type errors indicated by TS 4.7.4. (#163066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary TypeScript 4.7.4 shows some errors in our codebase, and the errors are suppressed in PR #162738. This will be merged **after** the linked PR. This PR modifies the code so there's no need for suppressing the errors. There were 2 lint errors I couldn't fix. --- 1️⃣ The following one I didn't manage to fix without a deeper refactor ([link](https://github.com/gergoabraham/kibana/blob/chore/defend-workflows-fix-ts-4.7.4-type-errors/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx#L219)) ```ts // x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx const store = createStore( mockGlobalState, storeReducer, kibanaObservable, storage, // @ts-expect-error ts upgrade v4.7.4 [...managementMiddlewareFactory(coreStart, depsStart), middlewareSpy.actionSpyMiddleware] ); ``` --- 2️⃣ Also, I have no idea how to fix this ([link](https://github.com/gergoabraham/kibana/blob/chore/defend-workflows-fix-ts-4.7.4-type-errors/x-pack/plugins/lists/server/services/extension_points/types.ts#L188)): ```ts pipeRun< T extends ExtensionPoint['type'], D extends NarrowExtensionPointToType = NarrowExtensionPointToType, // @ts-expect-error ts upgrade v4.7.4 P extends Parameters = Parameters >( extensionType: T, initialCallbackInput: P[0]['data'], callbackContext: ServerExtensionCallbackContext, callbackResponseValidator?: (data: P[0]['data']) => Error | undefined ): Promise; ``` Tried by merging `D` and `P`, or by creating a helper type using `infer` to unwrap the type of `data`, but anyway I got type errors, so after some hours, I just let this go. Please let me know if you got an idea. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lazy_osquery_action_params_form.tsx | 3 ++- .../osquery/osquery_response_action.tsx | 9 ++------- .../components/page_overlay/page_overlay.tsx | 2 -- .../exception_operators_data_types/keyword_array.ts | 11 +++++------ 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx index af7ad511b061b..e14b73242d6d5 100644 --- a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx +++ b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action_params_form.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { EuiLoadingSpinner } from '@elastic/eui'; import React, { lazy, Suspense } from 'react'; import type { OsqueryResponseActionsParamsFormProps } from './osquery_response_action_type'; @@ -16,7 +17,7 @@ export const getLazyOsqueryResponseActionTypeForm = const { onError, defaultValues, onChange } = props; return ( - + }> <>; export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionProps) => { const { osquery, application } = useKibana().services; - const OsqueryForm = useMemo( - () => osquery?.OsqueryResponseActionTypeForm, - [osquery?.OsqueryResponseActionTypeForm] - ); const isMounted = useIsMounted(); // serverless component that is returned when users do not have Endpoint.Complete tier @@ -85,8 +81,7 @@ export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionPro ); } - // @ts-expect-error ts upgrade v4.7.4 - if (isMounted() && OsqueryForm) { + if (isMounted()) { return ( ( useEffect(() => { if ( isMounted() && - // @ts-expect-error ts upgrade v4.7.4 - onHide && hideOnUrlPathnameChange && !isHidden && openedOnPathName && diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts index 7d95f6c9ec6bc..ff3611b4ab583 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group8/exception_operators_data_types/keyword_array.ts @@ -154,8 +154,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - // @ts-expect-error ts upgrade v4.7.4 - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -283,7 +282,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -345,7 +344,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -525,7 +524,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); @@ -695,7 +694,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForSignalsToBePresent(supertest, log, 1, [id]); const signalsOpen = await getSignalsById(supertest, log, id); const hits = signalsOpen.hits.hits.map((hit) => hit._source?.keyword).sort(); - expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]); + expect(hits.flat(10)).to.eql([]); }); }); From 9e4614647aaf5080e5f18ae7e08370ba3f9ce46f Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:37:15 +0200 Subject: [PATCH 06/58] added restart upgrade action (#166154) ## Summary Ready to review, tests are WIP Resolves https://github.com/elastic/kibana/issues/135539 For agents in `Updating` state, adding a `Restart upgrade` action, which uses the force flag on the API to bypass the updating check. For single agent, the action is only added if the agent has started upgrade for more than 2 hours (see discussion in [issue](https://github.com/elastic/kibana/issues/135539#issuecomment-1716136658)) For bulk selection, the action appears if all selected agents are in updating state. To verify: - Start local es, kibana, fleet server (can be docker) - Enroll agents with horde - update agent docs with the queries below to simulate stuck in updating for more than 2 hours (modify the query to target specific agents) - bulk select agents and check that `Restart upgrade` action is visible, and it triggers another upgrade with force flag only on the agents stuck in updating - when calling the bulk_upgrade API, the UI adds the updating condition to the query / or includes only those agent ids that are stuck in updating, depending on query or manual selection ``` curl -sk -XPOST --user elastic:changeme -H 'content-type:application/json' \ http://localhost:9200/_security/role/fleet_superuser -d ' { "indices": [ { "names": [".fleet*",".kibana*"], "privileges": ["all"], "allow_restricted_indices": true } ] }' curl -sk -XPOST --user elastic:changeme -H 'content-type:application/json' \ http://localhost:9200/_security/user/fleet_superuser -d ' { "password": "password", "roles": ["superuser", "fleet_superuser"] }' curl -sk -XPOST --user fleet_superuser:password -H 'content-type:application/json' \ -H'x-elastic-product-origin:fleet' \ http://localhost:9200/.fleet-agents/_update_by_query -d ' { "script": { "source": "ctx._source.upgrade_started_at = \"2023-09-13T11:26:23Z\"", "lang": "painless" }, "query": { "exists": { "field": "tags" } } }' ``` Agent details action: image Agent list, bulk action: image Agent list, single agent action: image Agent details callout: image Select all agents on first page, restart upgrade modal shows only those that are stuck in upgrading: image Select all agents on all pages, restart upgrade modal shows only those that are stuck upgrading, with an extra api call to get the count: image ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../fleet/common/services/agent_status.ts | 12 ++ .../fleet/common/types/rest_spec/agent.ts | 2 + .../components/action_menu.test.tsx | 41 ++++++ .../components/actions_menu.tsx | 21 +++ .../agent_details/agent_details_overview.tsx | 2 +- .../components/bulk_actions.test.tsx | 5 + .../components/bulk_actions.tsx | 32 ++++- .../components/table_row_actions.test.tsx | 47 +++++++ .../components/table_row_actions.tsx | 20 +++ .../sections/agents/agent_list_page/index.tsx | 1 + .../agents/components/agent_health.test.tsx | 76 +++++++++++ .../agents/components/agent_health.tsx | 128 +++++++++++++++--- .../agent_upgrade_modal/index.test.tsx | 103 +++++++++++--- .../components/agent_upgrade_modal/index.tsx | 84 +++++++++++- 14 files changed, 520 insertions(+), 54 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.test.tsx diff --git a/x-pack/plugins/fleet/common/services/agent_status.ts b/x-pack/plugins/fleet/common/services/agent_status.ts index 2ea2bef9d69df..a7a5257c603b8 100644 --- a/x-pack/plugins/fleet/common/services/agent_status.ts +++ b/x-pack/plugins/fleet/common/services/agent_status.ts @@ -56,3 +56,15 @@ export function buildKueryForUpdatingAgents(): string { export function buildKueryForInactiveAgents() { return 'status:inactive'; } + +export const AGENT_UPDATING_TIMEOUT_HOURS = 2; + +export function isStuckInUpdating(agent: Agent): boolean { + return ( + agent.status === 'updating' && + !!agent.upgrade_started_at && + !agent.upgraded_at && + Date.now() - Date.parse(agent.upgrade_started_at) > + AGENT_UPDATING_TIMEOUT_HOURS * 60 * 60 * 1000 + ); +} diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 07bb496434f1f..1e0e82d6c2ecd 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -101,6 +101,7 @@ export interface PostAgentUpgradeRequest { body: { source_uri?: string; version: string; + force?: boolean; }; } @@ -111,6 +112,7 @@ export interface PostBulkAgentUpgradeRequest { version: string; rollout_duration_seconds?: number; start_time?: string; + force?: boolean; }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx index b9ca2d64493e4..51c2250e0fe16 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx @@ -163,3 +163,44 @@ describe('AgentDetailsActionMenu', () => { }); }); }); + +describe('Restart upgrade action', () => { + function renderAndGetRestartUpgradeButton({ + agent, + agentPolicy, + }: { + agent: Agent; + agentPolicy?: AgentPolicy; + }) { + const { utils } = renderActions({ + agent, + agentPolicy, + }); + + return utils.queryByTestId('restartUpgradeBtn'); + } + + it('should render an active button', async () => { + const res = renderAndGetRestartUpgradeButton({ + agent: { + status: 'updating', + upgrade_started_at: '2022-11-21T12:27:24Z', + } as any, + agentPolicy: {} as AgentPolicy, + }); + + expect(res).not.toBe(null); + expect(res).toBeEnabled(); + }); + + it('should not render action if agent is not stuck in updating', async () => { + const res = renderAndGetRestartUpgradeButton({ + agent: { + status: 'updating', + upgrade_started_at: new Date().toISOString(), + } as any, + agentPolicy: {} as AgentPolicy, + }); + expect(res).toBe(null); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index 80651ad5a4ab3..251a5407de045 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -10,6 +10,7 @@ import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { isAgentRequestDiagnosticsSupported } from '../../../../../../../common/services'; +import { isStuckInUpdating } from '../../../../../../../common/services/agent_status'; import type { Agent, AgentPolicy } from '../../../../types'; import { useAuthz, useKibanaVersion } from '../../../../hooks'; @@ -41,6 +42,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false); const [isAgentDetailsJsonFlyoutOpen, setIsAgentDetailsJsonFlyoutOpen] = useState(false); const isUnenrolling = agent.status === 'unenrolling'; + const isAgentUpdating = isStuckInUpdating(agent); const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); const onContextMenuChange = useCallback( @@ -114,6 +116,24 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ ); } + if (isAgentUpdating) { + menuItems.push( + { + setIsUpgradeModalOpen(true); + }} + key="restartUpgradeAgent" + data-test-subj="restartUpgradeBtn" + > + + + ); + } + menuItems.push( )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 617ee8188c025..55a73c00d0688 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -129,7 +129,7 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ title: i18n.translate('xpack.fleet.agentDetails.statusLabel', { defaultMessage: 'Status', }), - description: , + description: , }, { title: i18n.translate('xpack.fleet.agentDetails.lastActivityLabel', { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx index 2d07cdb98f01d..37f67dd9e6a41 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.test.tsx @@ -80,6 +80,7 @@ describe('AgentBulkActions', () => { expect(results.getByText('Upgrade 2 agents').closest('button')!).toBeDisabled(); expect(results.getByText('Schedule upgrade for 2 agents').closest('button')!).toBeDisabled(); expect(results.queryByText('Request diagnostics for 2 agents')).toBeNull(); + expect(results.getByText('Restart upgrade 2 agents').closest('button')!).toBeDisabled(); }); it('should show available actions for 2 selected agents if they are active', async () => { @@ -112,6 +113,7 @@ describe('AgentBulkActions', () => { expect(results.getByText('Unenroll 2 agents').closest('button')!).toBeEnabled(); expect(results.getByText('Upgrade 2 agents').closest('button')!).toBeEnabled(); expect(results.getByText('Schedule upgrade for 2 agents').closest('button')!).toBeDisabled(); + expect(results.getByText('Restart upgrade 2 agents').closest('button')!).toBeEnabled(); }); it('should add actions if mockedExperimentalFeaturesService is enabled', async () => { @@ -202,6 +204,7 @@ describe('AgentBulkActions', () => { expect( results.getByText('Request diagnostics for 10 agents').closest('button')! ).toBeEnabled(); + expect(results.getByText('Restart upgrade 10 agents').closest('button')!).toBeEnabled(); }); it('should show correct actions for the active agents and exclude the managed agents from the count', async () => { @@ -255,6 +258,7 @@ describe('AgentBulkActions', () => { expect( results.getByText('Request diagnostics for 8 agents').closest('button')! ).toBeEnabled(); + expect(results.getByText('Restart upgrade 8 agents').closest('button')!).toBeEnabled(); }); it('should show correct actions when no managed policies exist', async () => { @@ -292,6 +296,7 @@ describe('AgentBulkActions', () => { expect( results.getByText('Request diagnostics for 10 agents').closest('button')! ).toBeEnabled(); + expect(results.getByText('Restart upgrade 10 agents').closest('button')!).toBeEnabled(); }); it('should generate a correct kuery to select agents', async () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx index e5a33beba733b..03ffc5fd615ee 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/bulk_actions.tsx @@ -73,7 +73,11 @@ export const AgentBulkActions: React.FunctionComponent = ({ // Actions states const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false); - const [updateModalState, setUpgradeModalState] = useState({ isOpen: false, isScheduled: false }); + const [updateModalState, setUpgradeModalState] = useState({ + isOpen: false, + isScheduled: false, + isUpdating: false, + }); const [isTagAddVisible, setIsTagAddVisible] = useState(false); const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false); @@ -219,7 +223,7 @@ export const AgentBulkActions: React.FunctionComponent = ({ disabled: !atLeastOneActiveAgentSelected, onClick: () => { closeMenu(); - setUpgradeModalState({ isOpen: true, isScheduled: false }); + setUpgradeModalState({ isOpen: true, isScheduled: false, isUpdating: false }); }, }, { @@ -237,11 +241,30 @@ export const AgentBulkActions: React.FunctionComponent = ({ disabled: !atLeastOneActiveAgentSelected || !isLicenceAllowingScheduleUpgrade, onClick: () => { closeMenu(); - setUpgradeModalState({ isOpen: true, isScheduled: true }); + setUpgradeModalState({ isOpen: true, isScheduled: true, isUpdating: false }); }, }, ]; + menuItems.push({ + name: ( + + ), + icon: , + disabled: !atLeastOneActiveAgentSelected, + onClick: () => { + closeMenu(); + setUpgradeModalState({ isOpen: true, isScheduled: false, isUpdating: true }); + }, + }); + if (diagnosticFileUploadEnabled) { menuItems.push({ name: ( @@ -306,8 +329,9 @@ export const AgentBulkActions: React.FunctionComponent = ({ agents={agents} agentCount={agentCount} isScheduled={updateModalState.isScheduled} + isUpdating={updateModalState.isUpdating} onClose={() => { - setUpgradeModalState({ isOpen: false, isScheduled: false }); + setUpgradeModalState({ isOpen: false, isScheduled: false, isUpdating: false }); refreshAgents(); }} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx index f58ebc3977c8b..17ebb6a6631f9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx @@ -132,4 +132,51 @@ describe('TableRowActions', () => { expect(res).not.toBeEnabled(); }); }); + + describe('Restart upgrade action', () => { + function renderAndGetRestartUpgradeButton({ + agent, + agentPolicy, + }: { + agent: Agent; + agentPolicy?: AgentPolicy; + }) { + const { utils } = renderTableRowActions({ + agent, + agentPolicy, + }); + + return utils.queryByTestId('restartUpgradeBtn'); + } + + it('should render an active button', async () => { + const res = renderAndGetRestartUpgradeButton({ + agent: { + active: true, + status: 'updating', + upgrade_started_at: '2022-11-21T12:27:24Z', + } as any, + agentPolicy: { + is_managed: false, + } as AgentPolicy, + }); + + expect(res).not.toBe(null); + expect(res).toBeEnabled(); + }); + + it('should not render action if agent is not stuck in updating', async () => { + const res = renderAndGetRestartUpgradeButton({ + agent: { + active: true, + status: 'updating', + upgrade_started_at: new Date().toISOString(), + } as any, + agentPolicy: { + is_managed: false, + } as AgentPolicy, + }); + expect(res).toBe(null); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index 5f82d3b556897..6b36f0a29d587 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -11,6 +11,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { isAgentRequestDiagnosticsSupported } from '../../../../../../../common/services'; +import { isStuckInUpdating } from '../../../../../../../common/services/agent_status'; + import type { Agent, AgentPolicy } from '../../../../types'; import { useAuthz, useLink, useKibanaVersion } from '../../../../hooks'; import { ContextMenuActions } from '../../../../components'; @@ -117,6 +119,24 @@ export const TableRowActions: React.FunctionComponent<{ ); + if (isStuckInUpdating(agent)) { + menuItems.push( + { + onUpgradeClick(); + }} + data-test-subj="restartUpgradeBtn" + > + + + ); + } + if (agentTamperProtectionEnabled && agent.policy_id) { menuItems.push( = () => { setAgentToUpgrade(undefined); refreshAgents(); }} + isUpdating={Boolean(agentToUpgrade.upgrade_started_at && !agentToUpgrade.upgraded_at)} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.test.tsx new file mode 100644 index 0000000000000..93646a544bebb --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.test.tsx @@ -0,0 +1,76 @@ +/* + * 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 React from 'react'; + +import { act, fireEvent } from '@testing-library/react'; + +import { createFleetTestRendererMock } from '../../../../../mock'; + +import type { Agent } from '../../../types'; + +import { AgentHealth } from './agent_health'; + +jest.mock('./agent_upgrade_modal', () => { + return { + AgentUpgradeAgentModal: () => <>Upgrade Modal, + }; +}); + +function renderAgentHealth(agent: Agent, fromDetails?: boolean) { + const renderer = createFleetTestRendererMock(); + + const utils = renderer.render(); + + return { utils }; +} + +describe('AgentHealth', () => { + it('should render agent health with callout when agent stuck updating', () => { + const { utils } = renderAgentHealth( + { + active: true, + status: 'updating', + upgrade_started_at: '2022-11-21T12:27:24Z', + } as any, + true + ); + + act(() => { + fireEvent.click(utils.getByTestId('restartUpgradeBtn')); + }); + + utils.findByText('Upgrade Modal'); + }); + + it('should not render agent health with callout when agent not stuck updating', () => { + const { utils } = renderAgentHealth( + { + active: true, + status: 'updating', + upgrade_started_at: new Date().toISOString(), + } as any, + true + ); + + expect(utils.queryByTestId('restartUpgradeBtn')).not.toBeInTheDocument(); + }); + + it('should not render agent health with callout when not from details', () => { + const { utils } = renderAgentHealth( + { + active: true, + status: 'updating', + upgrade_started_at: '2022-11-21T12:27:24Z', + } as any, + false + ); + + expect(utils.queryByTestId('restartUpgradeBtn')).not.toBeInTheDocument(); + expect(utils.container.querySelector('[data-euiicon-type="warning"]')).not.toBeNull(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index 9674480ab8bfe..9aefdff0d0578 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -5,19 +5,35 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; +import styled from 'styled-components'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; -import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { + EuiBadge, + EuiButton, + EuiCallOut, + EuiIcon, + EuiPortal, + EuiSpacer, + EuiToolTip, +} from '@elastic/eui'; import { euiLightVars as euiVars } from '@kbn/ui-theme'; -import { getPreviousAgentStatusForOfflineAgents } from '../../../../../../common/services/agent_status'; +import { + getPreviousAgentStatusForOfflineAgents, + isStuckInUpdating, +} from '../../../../../../common/services/agent_status'; import type { Agent } from '../../../types'; +import { useAgentRefresh } from '../agent_details_page/hooks'; + +import { AgentUpgradeAgentModal } from './agent_upgrade_modal'; + interface Props { agent: Agent; - showOfflinePreviousStatus?: boolean; + fromDetails?: boolean; } const Status = { @@ -79,10 +95,11 @@ function getStatusComponent(status: Agent['status']): React.ReactElement { } } -export const AgentHealth: React.FunctionComponent = ({ - agent, - showOfflinePreviousStatus, -}) => { +const WrappedEuiCallOut = styled(EuiCallOut)` + white-space: wrap !important; +`; + +export const AgentHealth: React.FunctionComponent = ({ agent, fromDetails }) => { const { last_checkin: lastCheckIn, last_checkin_message: lastCheckInMessage } = agent; const msLastCheckIn = new Date(lastCheckIn || 0).getTime(); const lastCheckInMessageText = lastCheckInMessage ? ( @@ -112,27 +129,94 @@ export const AgentHealth: React.FunctionComponent = ({ ); const previousToOfflineStatus = useMemo(() => { - if (!showOfflinePreviousStatus || agent.status !== 'offline') { + if (!fromDetails || agent.status !== 'offline') { return; } return getPreviousAgentStatusForOfflineAgents(agent); - }, [showOfflinePreviousStatus, agent]); + }, [fromDetails, agent]); + + const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); + const refreshAgent = useAgentRefresh(); return ( - + +

{lastCheckinText}

+

{lastCheckInMessageText}

+ {isStuckInUpdating(agent) ? ( +

+ +

+ ) : null} + + } + > + <> + {getStatusComponent(agent.status)} + {previousToOfflineStatus ? getStatusComponent(previousToOfflineStatus) : null} + {isStuckInUpdating(agent) && !fromDetails ? ( + <> +   + + + ) : null} + +
+ {fromDetails && isStuckInUpdating(agent) ? ( <> -

{lastCheckinText}

-

{lastCheckInMessageText}

+ + + } + > +

+ +

+ { + setIsUpgradeModalOpen(true); + }} + data-test-subj="restartUpgradeBtn" + > + + +
- } - > - <> - {getStatusComponent(agent.status)} - {previousToOfflineStatus ? getStatusComponent(previousToOfflineStatus) : null} - -
+ ) : null} + {isUpgradeModalOpen && ( + + { + setIsUpgradeModalOpen(false); + refreshAgent(); + }} + isUpdating={true} + /> + + )} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.test.tsx index 78619b976c49f..a3c50e0b9aee7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.test.tsx @@ -7,20 +7,15 @@ import React from 'react'; -import { waitFor } from '@testing-library/react'; +import { act, fireEvent, waitFor } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; +import { sendPostBulkAgentUpgrade } from '../../../../hooks'; + import { AgentUpgradeAgentModal } from '.'; import type { AgentUpgradeAgentModalProps } from '.'; -jest.mock('@elastic/eui', () => { - return { - ...jest.requireActual('@elastic/eui'), - EuiConfirmModal: ({ children }: any) => <>{children}, - }; -}); - jest.mock('../../../../hooks', () => { return { ...jest.requireActual('../../../../hooks'), @@ -29,9 +24,15 @@ jest.mock('../../../../hooks', () => { items: ['8.7.0'], }, }), + sendGetAgentStatus: jest.fn().mockResolvedValue({ + data: { results: { updating: 2 } }, + }), + sendPostBulkAgentUpgrade: jest.fn(), }; }); +const mockSendPostBulkAgentUpgrade = sendPostBulkAgentUpgrade as jest.Mock; + function renderAgentUpgradeAgentModal(props: Partial) { const renderer = createFleetTestRendererMock(); @@ -41,6 +42,7 @@ function renderAgentUpgradeAgentModal(props: Partial { it('should set the default to Immediately if there is less than 10 agents using kuery', async () => { const { utils } = renderAgentUpgradeAgentModal({ @@ -48,10 +50,7 @@ describe('AgentUpgradeAgentModal', () => { agentCount: 3, }); - const el = utils.container.querySelector( - '[data-test-subj="agentUpgradeModal.MaintenanceCombobox"]' - ); - expect(el).not.toBeNull(); + const el = utils.getByTestId('agentUpgradeModal.MaintenanceCombobox'); expect(el?.textContent).toBe('Immediately'); }); @@ -61,10 +60,7 @@ describe('AgentUpgradeAgentModal', () => { agentCount: 3, }); - const el = utils.container.querySelector( - '[data-test-subj="agentUpgradeModal.MaintenanceCombobox"]' - ); - expect(el).not.toBeNull(); + const el = utils.getByTestId('agentUpgradeModal.MaintenanceCombobox'); expect(el?.textContent).toBe('Immediately'); }); @@ -74,11 +70,7 @@ describe('AgentUpgradeAgentModal', () => { agentCount: 13, }); - const el = utils.container.querySelector( - '[data-test-subj="agentUpgradeModal.MaintenanceCombobox"]' - ); - - expect(el).not.toBeNull(); + const el = utils.getByTestId('agentUpgradeModal.MaintenanceCombobox'); expect(el?.textContent).toBe('1 hour'); }); @@ -93,4 +85,73 @@ describe('AgentUpgradeAgentModal', () => { expect(el.classList.contains('euiComboBox-isDisabled')).toBe(false); }); }); + + it('should restart uprade on updating agents if some agents in updating', async () => { + const { utils } = renderAgentUpgradeAgentModal({ + agents: [ + { status: 'updating', upgrade_started_at: '2022-11-21T12:27:24Z', id: 'agent1' }, + { id: 'agent2' }, + ] as any, + agentCount: 2, + isUpdating: true, + }); + + const el = utils.getByTestId('confirmModalTitleText'); + expect(el.textContent).toEqual('Restart upgrade on 1 out of 2 agents stuck in updating'); + + const btn = utils.getByTestId('confirmModalConfirmButton'); + await waitFor(() => { + expect(btn).toBeEnabled(); + }); + + act(() => { + fireEvent.click(btn); + }); + + expect(mockSendPostBulkAgentUpgrade.mock.calls.at(-1)[0]).toEqual( + expect.objectContaining({ agents: ['agent1'], force: true }) + ); + }); + + it('should restart upgrade on updating agents if kuery', async () => { + const { utils } = renderAgentUpgradeAgentModal({ + agents: '*', + agentCount: 3, + isUpdating: true, + }); + + const el = await utils.findByTestId('confirmModalTitleText'); + expect(el.textContent).toEqual('Restart upgrade on 2 out of 3 agents stuck in updating'); + + const btn = utils.getByTestId('confirmModalConfirmButton'); + await waitFor(() => { + expect(btn).toBeEnabled(); + }); + + act(() => { + fireEvent.click(btn); + }); + + expect(mockSendPostBulkAgentUpgrade.mock.calls.at(-1)[0]).toEqual( + expect.objectContaining({ + agents: + '(*) AND status:updating AND upgrade_started_at:* AND NOT upgraded_at:* AND upgrade_started_at < now-2h', + force: true, + }) + ); + }); + + it('should disable submit button if no agents stuck updating', () => { + const { utils } = renderAgentUpgradeAgentModal({ + agents: [ + { status: 'offline', upgrade_started_at: '2022-11-21T12:27:24Z', id: 'agent1' }, + { id: 'agent2' }, + ] as any, + agentCount: 2, + isUpdating: true, + }); + + const el = utils.getByTestId('confirmModalConfirmButton'); + expect(el).toBeDisabled(); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index 014075b1f0241..c4342d7436e22 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -28,6 +28,11 @@ import semverGt from 'semver/functions/gt'; import semverLt from 'semver/functions/lt'; import { getMinVersion } from '../../../../../../../common/services/get_min_max_version'; +import { + AGENT_UPDATING_TIMEOUT_HOURS, + isStuckInUpdating, +} from '../../../../../../../common/services/agent_status'; + import type { Agent } from '../../../../types'; import { sendPostAgentUpgrade, @@ -35,6 +40,7 @@ import { useStartServices, useKibanaVersion, useConfig, + sendGetAgentStatus, } from '../../../../hooks'; import { sendGetAgentsAvailableVersions } from '../../../../hooks'; @@ -51,6 +57,7 @@ export interface AgentUpgradeAgentModalProps { agents: Agent[] | string; agentCount: number; isScheduled?: boolean; + isUpdating?: boolean; } const getVersion = (version: Array>) => version[0]?.value as string; @@ -68,6 +75,7 @@ export const AgentUpgradeAgentModal: React.FunctionComponent { const { notifications } = useStartServices(); const kibanaVersion = useKibanaVersion() || ''; @@ -80,6 +88,47 @@ export const AgentUpgradeAgentModal: React.FunctionComponent(0); + const [updatingQuery, setUpdatingQuery] = useState(''); + + const QUERY_STUCK_UPDATING = `status:updating AND upgrade_started_at:* AND NOT upgraded_at:* AND upgrade_started_at < now-${AGENT_UPDATING_TIMEOUT_HOURS}h`; + + useEffect(() => { + const getStuckUpdatingAgentCount = async (agentsOrQuery: Agent[] | string) => { + let newQuery; + // find updating agents from array + if (Array.isArray(agentsOrQuery) && agentsOrQuery.length > 0) { + if (agentsOrQuery.length === 0) { + return; + } + const newAgents = agentsOrQuery.filter((agent) => isStuckInUpdating(agent)); + const updatingCount = newAgents.length; + setUpdatingAgents(updatingCount); + setUpdatingQuery(newAgents); + return; + } else if (typeof agentsOrQuery === 'string' && agentsOrQuery !== '') { + newQuery = [`(${agentsOrQuery})`, QUERY_STUCK_UPDATING].join(' AND '); + } else { + newQuery = QUERY_STUCK_UPDATING; + } + setUpdatingQuery(newQuery); + + // if selection is a query, do an api call to get updating agents + try { + const res = await sendGetAgentStatus({ + kuery: newQuery, + }); + setUpdatingAgents(res?.data?.results?.updating ?? 0); + } catch (err) { + return; + } + }; + + if (!isUpdating) return; + + getStuckUpdatingAgentCount(agents); + }, [isUpdating, setUpdatingQuery, QUERY_STUCK_UPDATING, agents]); + useEffect(() => { const getVersions = async () => { try { @@ -166,14 +215,18 @@ export const AgentUpgradeAgentModal: React.FunctionComponent + Array.isArray(agentsOrQuery) ? agentsOrQuery.map((agent) => agent.id) : agentsOrQuery; const { error } = isSingleAgent && !isScheduled ? await sendPostAgentUpgrade((agents[0] as Agent).id, { version, + force: isUpdating, }) : await sendPostBulkAgentUpgrade({ version, - agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, + agents: getQuery(isUpdating ? updatingQuery : agents), + force: isUpdating, ...rolloutOptions, }); if (error) { @@ -219,16 +272,29 @@ export const AgentUpgradeAgentModal: React.FunctionComponent {isSingleAgent ? ( - + isUpdating ? ( + + ) : ( + + ) ) : isScheduled ? ( + ) : isUpdating ? ( + ) : ( } - confirmButtonDisabled={isSubmitting || noVersions} + confirmButtonDisabled={isSubmitting || noVersions || (isUpdating && updatingAgents === 0)} confirmButtonText={ isSingleAgent ? ( + ) : isUpdating ? ( + ) : ( Date: Mon, 18 Sep 2023 14:47:36 +0300 Subject: [PATCH 07/58] [APM] Add elastic-api-version header to Kibana API calls (#166602) Versioned APIs in Kibana require an `elastic-api-version` header. This PR adds the required header to Synthtrace. --- .../src/lib/apm/client/apm_synthtrace_kibana_client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts index bfae0c76d76a8..3304fc7bd3c9c 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts @@ -69,5 +69,6 @@ function kibanaHeaders() { Accept: 'application/json', 'Content-Type': 'application/json', 'kbn-xsrf': 'kibana', + 'elastic-api-version': '2023-10-31', }; } From 70c7fc571b714f9bc00f6d2c71c1fd13c587caad Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 18 Sep 2023 08:31:16 -0400 Subject: [PATCH 08/58] Upgrade openpgp 5.3.0 to 5.10.1 (#165526) ## Summary Upgrade `openpgp` from `5.3.0` to `5.10.1` Commit log: https://github.com/openpgpjs/openpgpjs/compare/v5.3.0...v5.10.1 There is an incompatibility of `Uint8Array` when using Jest/JSDom with the TextEncoder/TextDecoder from node `util`. `https://github.com/kayahr/text-encoding` has been added as a `devDependency` so it can be used in the polyfill. It provides a working TextEncoder/Decoder for our Jest tests. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 3 ++- .../src/jest/setup/polyfills.jsdom.js | 6 +++--- renovate.json | 5 +++-- src/dev/license_checker/config.ts | 2 +- yarn.lock | 20 +++++++++++++++---- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 220d685d29cab..d3f6a91735108 100644 --- a/package.json +++ b/package.json @@ -952,7 +952,7 @@ "object-hash": "^1.3.1", "object-path-immutable": "^3.1.1", "openai": "^3.3.0", - "openpgp": "5.3.0", + "openpgp": "5.10.1", "opn": "^5.5.0", "ora": "^4.0.4", "p-limit": "^3.0.1", @@ -1098,6 +1098,7 @@ "@jest/reporters": "^29.6.1", "@jest/transform": "^29.6.1", "@jest/types": "^29.6.1", + "@kayahr/text-encoding": "^1.2.0", "@kbn/alerting-api-integration-helpers": "link:x-pack/test/alerting_api_integration/packages/helpers", "@kbn/ambient-common-types": "link:packages/kbn-ambient-common-types", "@kbn/ambient-ftr-types": "link:packages/kbn-ambient-ftr-types", diff --git a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js index 1d963afdfc4da..77aa4a6e389d1 100644 --- a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js +++ b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js @@ -17,9 +17,9 @@ if (!global.URL.hasOwnProperty('createObjectURL')) { // https://github.com/jsdom/jsdom/issues/2524 if (!global.hasOwnProperty('TextEncoder')) { - const { TextEncoder, TextDecoder } = require('util'); - global.TextEncoder = TextEncoder; - global.TextDecoder = TextDecoder; + const customTextEncoding = require('@kayahr/text-encoding'); + global.TextEncoder = customTextEncoding.TextEncoder; + global.TextDecoder = customTextEncoding.TextDecoder; } // NOTE: We should evaluate removing this once we upgrade to Node 18 and find out if loaders.gl already fixed this usage diff --git a/renovate.json b/renovate.json index 7d6ccf6a22c88..0fd6ad800c03c 100644 --- a/renovate.json +++ b/renovate.json @@ -286,7 +286,8 @@ "tough-cookie", "@types/tough-cookie", "xml-crypto", - "@types/xml-crypto" + "@types/xml-crypto", + "@kayahr/text-encoding" ], "reviewers": [ "team:kibana-security" @@ -611,4 +612,4 @@ "enabled": true } ] -} \ No newline at end of file +} diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index ff0296ec9777b..d7c04f430661c 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -78,7 +78,7 @@ export const DEV_ONLY_LICENSE_ALLOWED = ['MPL-2.0']; // there are some licenses which should not be globally allowed // but can be brought in on a per-package basis export const PER_PACKAGE_ALLOWED_LICENSES = { - 'openpgp@5.3.0': ['LGPL-3.0+'], + 'openpgp@5.10.1': ['LGPL-3.0+'], }; // Globally overrides a license for a given package@version export const LICENSE_OVERRIDES = { diff --git a/yarn.lock b/yarn.lock index e9c20dd8ff99a..8416457ad4a45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2950,6 +2950,13 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== +"@kayahr/text-encoding@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@kayahr/text-encoding/-/text-encoding-1.2.0.tgz#9d75de6b40d7694e524c8ce39fc6e08994680746" + integrity sha512-61R84DjOQvO4bakOl4Vwuw0wU3FLbFtfUf4ApJquQ2+N3AY2VlN0j9te8rpGFHx2mzvhWKetyDgVZiLeU2/dhA== + dependencies: + tslib "^2.5.2" + "@kbn/aad-fixtures-plugin@link:x-pack/test/alerting_api_integration/common/plugins/aad": version "0.0.0" uid "" @@ -23394,10 +23401,10 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -openpgp@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.3.0.tgz#e8fc97e538865b8c095dbd91c7be4203bd1dd1df" - integrity sha512-qjCj0vYpV3dmmkE+vURiJ5kVAJwrMk8BPukvpWJiHcTNWKwPVsRS810plIe4klIcHVf1ScgUQwqtBbv99ff+kQ== +openpgp@5.10.1: + version "5.10.1" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.10.1.tgz#3b137470187b79281719ced16fb9e60b822cfd24" + integrity sha512-SR5Ft+ej51d0+p53ld5Ney0Yiz0y8Mh1YYLJrvpRMbTaNhvS1QcDX0Oq1rW9sjBnQXtgrpWw2Zve3rm7K5C/pw== dependencies: asn1.js "^5.0.0" @@ -29154,6 +29161,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== +tslib@^2.5.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" From 3cfd6b540939d797a4aaa6e9903bde9cfb256e60 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Mon, 18 Sep 2023 14:39:45 +0200 Subject: [PATCH 09/58] [Log Explorer] Fix embedded Discover responsive view (#166429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #166243 Removing the additional flex item for the DiscoverContainer restores the auto resizing for the container to correctly keep the Discover content responsive to viewport changes. https://github.com/elastic/kibana/assets/34506779/645d0c66-fce3-4e98-9d8f-76548153f603 --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/discover_container/discover_container.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/public/components/discover_container/discover_container.tsx b/src/plugins/discover/public/components/discover_container/discover_container.tsx index 28a596946bd18..8c879fdfef7bc 100644 --- a/src/plugins/discover/public/components/discover_container/discover_container.tsx +++ b/src/plugins/discover/public/components/discover_container/discover_container.tsx @@ -82,7 +82,11 @@ export const DiscoverContainerInternal = ({ css={discoverContainerWrapperCss} data-test-subj="discover-container-internal-wrapper" > - + Date: Mon, 18 Sep 2023 14:51:41 +0200 Subject: [PATCH 10/58] [AO] Rename "Threshold rule" to "Custom threshold (BETA)" (#166145) ## Summary Fixes #166143 Fixes https://github.com/elastic/actionable-observability/issues/115 ### Expected Screenshot 2023-09-11 at 11 48 40 --- .../plugins/observability/common/constants.ts | 2 +- .../color_palette.ts | 0 .../constants.ts | 0 .../formatters/bytes.test.ts | 0 .../formatters/bytes.ts | 0 .../formatters/datetime.ts | 0 .../formatters/high_precision.ts | 0 .../formatters/index.ts | 0 .../formatters/number.ts | 0 .../formatters/percent.ts | 0 .../formatters/snapshot_metric_formats.ts | 0 .../formatters/types.ts | 0 .../metric_value_formatter.test.ts | 0 .../metric_value_formatter.ts | 2 +- .../metrics_explorer.ts | 14 +-- .../types.ts | 0 .../alert_details_app_section.test.tsx.snap | 0 .../alert_details_app_section.test.tsx | 0 .../components/alert_details_app_section.tsx | 10 +-- .../components/alert_flyout.tsx | 2 +- .../closable_popover_title.test.tsx | 0 .../components/closable_popover_title.tsx | 0 .../criterion_preview_chart.tsx | 16 ++-- .../threshold_annotations.test.tsx | 4 +- .../threshold_annotations.tsx | 4 +- .../custom_equation_editor.stories.tsx | 2 +- .../custom_equation_editor.tsx | 14 +-- .../components/custom_equation/index.tsx | 0 .../custom_equation/metric_row_controls.tsx | 0 .../custom_equation/metric_row_with_agg.tsx | 15 ++-- .../components/custom_equation/types.ts | 2 +- .../components/custom_threshold.stories.tsx} | 4 +- .../components/custom_threshold.test.tsx} | 4 +- .../components/custom_threshold.tsx} | 13 +-- .../components/expression_chart.test.tsx | 2 +- .../components/expression_chart.tsx | 10 +-- .../components/expression_row.test.tsx | 2 +- .../components/expression_row.tsx | 80 +++++++++++------ .../components/group_by.tsx | 0 .../components/metrics_alert_dropdown.tsx | 39 +++++---- .../components/series_chart.tsx | 6 +- .../components/triggers_actions_context.tsx | 0 .../components/validation.test.ts | 0 .../components/validation.tsx | 70 +++++++++------ ...custom_threshold_rule_expression.test.tsx} | 6 +- .../custom_threshold_rule_expression.tsx} | 37 ++++---- .../helpers/calculate_domain.ts | 4 +- .../helpers/corrected_percent_convert.test.ts | 0 .../helpers/corrected_percent_convert.ts | 0 .../helpers/create_formatter_for_metric.ts | 6 +- .../create_formatter_for_metrics.test.ts | 2 +- .../helpers/create_metric_label.test.ts | 2 +- .../helpers/create_metric_label.ts | 0 .../helpers/get_metric_id.ts | 2 +- .../helpers/kuery.ts | 0 .../helpers/metric_to_format.ts | 4 +- .../helpers/notifications.ts | 15 ++-- .../helpers/runtime_types.ts | 0 .../helpers/source_errors.ts | 2 +- .../helpers/use_alert_prefill.ts | 0 .../hooks/use_kibana_time_zone_setting.ts | 0 .../hooks/use_kibana_timefilter_time.tsx | 0 .../use_metric_threshold_alert_prefill.ts | 2 +- .../hooks/use_metrics_explorer_chart_data.ts | 4 +- .../hooks/use_metrics_explorer_data.test.tsx | 0 .../hooks/use_metrics_explorer_data.ts | 2 +- .../use_metrics_explorer_options.test.tsx | 0 .../hooks/use_metrics_explorer_options.ts | 4 +- .../hooks/use_tracked_promise.ts | 0 .../i18n_strings.ts | 12 +-- .../lib/generate_unique_key.test.ts | 2 +- .../lib/generate_unique_key.ts | 0 .../lib/transform_metrics_explorer_data.ts | 2 +- .../mocks/metric_threshold_rule.ts | 10 +-- .../rule_data_formatters.ts | 0 .../{threshold => custom_threshold}/types.ts | 4 +- .../with_kuery_autocompletion.tsx | 2 +- .../register_observability_rule_types.ts | 16 ++-- .../public/utils/metrics_explorer.ts | 4 +- x-pack/plugins/observability/server/index.ts | 2 +- .../custom_threshold_executor.test.ts} | 6 +- .../custom_threshold_executor.ts} | 27 +++--- .../lib/check_missing_group.ts | 2 +- ...onvert_strings_to_missing_groups_record.ts | 0 .../lib/create_bucket_selector.ts | 2 +- .../lib/create_condition_script.ts | 2 +- .../lib/create_custom_metrics_aggregations.ts | 2 +- .../lib/create_percentile_aggregation.ts | 2 +- .../lib/create_rate_aggregation.ts | 0 .../lib/create_timerange.test.ts | 2 +- .../lib/create_timerange.ts | 2 +- .../lib/evaluate_rule.ts | 4 +- .../lib/get_data.ts | 2 +- .../lib/metric_expression_params.ts | 2 +- .../lib/metric_query.test.ts | 2 +- .../lib/metric_query.ts | 5 +- .../lib/metrics_explorer.ts | 16 +--- .../lib/wrap_in_period.ts | 2 +- .../messages.ts | 60 +++++++------ .../register_custom_threshold_rule_type.ts} | 8 +- .../{threshold => custom_threshold}/types.ts | 2 +- .../utils.test.ts | 0 .../{threshold => custom_threshold}/utils.ts | 2 +- .../server/lib/rules/register_rule_types.ts | 2 +- .../translations/translations/fr-FR.json | 87 ------------------- .../translations/translations/ja-JP.json | 87 ------------------- .../translations/translations/zh-CN.json | 87 ------------------- .../avg_pct_fired.ts | 24 +++-- .../avg_pct_no_data.ts | 24 +++-- .../avg_us_fired.ts | 26 +++--- .../custom_eq_avg_bytes_fired.ts | 24 +++-- .../documents_count_fired.ts | 24 +++-- .../group_by_fired.ts | 26 +++--- ....ts => custom_threshold_rule_data_view.ts} | 7 +- .../helpers/alerting_api_helper.ts | 2 +- .../observability/index.ts | 14 +-- .../group4/check_registered_rule_types.ts | 2 +- .../api_integration/services/alerting_api.ts | 2 +- .../avg_pct_fired.ts | 24 +++-- .../avg_pct_no_data.ts | 24 +++-- .../custom_eq_avg_bytes_fired.ts | 24 +++-- .../documents_count_fired.ts | 24 +++-- .../group_by_fired.ts | 26 +++--- .../index.ts | 2 +- .../observability/index.feature_flags.ts | 2 +- 125 files changed, 520 insertions(+), 659 deletions(-) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/color_palette.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/constants.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/bytes.test.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/bytes.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/datetime.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/high_precision.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/index.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/number.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/percent.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/snapshot_metric_formats.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/formatters/types.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/metric_value_formatter.test.ts (100%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/metric_value_formatter.ts (89%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/metrics_explorer.ts (96%) rename x-pack/plugins/observability/common/{threshold_rule => custom_threshold_rule}/types.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/__snapshots__/alert_details_app_section.test.tsx.snap (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/alert_details_app_section.test.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/alert_details_app_section.tsx (95%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/alert_flyout.tsx (94%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/closable_popover_title.test.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/closable_popover_title.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/criterion_preview_chart/criterion_preview_chart.tsx (85%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/criterion_preview_chart/threshold_annotations.test.tsx (96%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/criterion_preview_chart/threshold_annotations.tsx (95%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/custom_equation_editor.stories.tsx (98%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/custom_equation_editor.tsx (92%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/index.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/metric_row_controls.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/metric_row_with_agg.tsx (91%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/custom_equation/types.ts (96%) rename x-pack/plugins/observability/public/components/{threshold/components/threshold.stories.tsx => custom_threshold/components/custom_threshold.stories.tsx} (88%) rename x-pack/plugins/observability/public/components/{threshold/components/threshold.test.tsx => custom_threshold/components/custom_threshold.test.tsx} (90%) rename x-pack/plugins/observability/public/components/{threshold/components/threshold.tsx => custom_threshold/components/custom_threshold.tsx} (81%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/expression_chart.test.tsx (96%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/expression_chart.tsx (94%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/expression_row.test.tsx (97%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/expression_row.tsx (82%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/group_by.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/metrics_alert_dropdown.tsx (80%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/series_chart.tsx (93%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/triggers_actions_context.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/validation.test.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/components/validation.tsx (76%) rename x-pack/plugins/observability/public/components/{threshold/threshold_rule_expression.test.tsx => custom_threshold/custom_threshold_rule_expression.test.tsx} (96%) rename x-pack/plugins/observability/public/components/{threshold/threshold_rule_expression.tsx => custom_threshold/custom_threshold_rule_expression.tsx} (92%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/calculate_domain.ts (92%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/corrected_percent_convert.test.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/corrected_percent_convert.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/create_formatter_for_metric.ts (76%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/create_formatter_for_metrics.test.ts (94%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/create_metric_label.test.ts (88%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/create_metric_label.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/get_metric_id.ts (93%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/kuery.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/metric_to_format.ts (80%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/notifications.ts (69%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/runtime_types.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/source_errors.ts (89%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/helpers/use_alert_prefill.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_kibana_time_zone_setting.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_kibana_timefilter_time.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metric_threshold_alert_prefill.ts (91%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metrics_explorer_chart_data.ts (97%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metrics_explorer_data.test.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metrics_explorer_data.ts (97%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metrics_explorer_options.test.tsx (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_metrics_explorer_options.ts (97%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/hooks/use_tracked_promise.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/i18n_strings.ts (65%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/lib/generate_unique_key.test.ts (93%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/lib/generate_unique_key.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/lib/transform_metrics_explorer_data.ts (91%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/mocks/metric_threshold_rule.ts (93%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/rule_data_formatters.ts (100%) rename x-pack/plugins/observability/public/components/{threshold => custom_threshold}/types.ts (97%) rename x-pack/plugins/observability/server/lib/rules/{threshold/threshold_executor.test.ts => custom_threshold/custom_threshold_executor.test.ts} (99%) rename x-pack/plugins/observability/server/lib/rules/{threshold/threshold_executor.ts => custom_threshold/custom_threshold_executor.ts} (94%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/check_missing_group.ts (96%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/convert_strings_to_missing_groups_record.ts (100%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_bucket_selector.ts (98%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_condition_script.ts (92%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_custom_metrics_aggregations.ts (98%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_percentile_aggregation.ts (87%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_rate_aggregation.ts (100%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_timerange.test.ts (98%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/create_timerange.ts (92%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/evaluate_rule.ts (96%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/get_data.ts (99%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/metric_expression_params.ts (93%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/metric_query.test.ts (98%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/metric_query.ts (98%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/metrics_explorer.ts (94%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/lib/wrap_in_period.ts (93%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/messages.ts (73%) rename x-pack/plugins/observability/server/lib/rules/{threshold/register_threshold_rule_type.ts => custom_threshold/register_custom_threshold_rule_type.ts} (97%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/types.ts (96%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/utils.test.ts (100%) rename x-pack/plugins/observability/server/lib/rules/{threshold => custom_threshold}/utils.ts (99%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/avg_pct_fired.ts (90%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/avg_pct_no_data.ts (90%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/avg_us_fired.ts (92%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/custom_eq_avg_bytes_fired.ts (91%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/documents_count_fired.ts (90%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule => custom_threshold_rule}/group_by_fired.ts (92%) rename x-pack/test/alerting_api_integration/observability/{threshold_rule_data_view.ts => custom_threshold_rule_data_view.ts} (96%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/avg_pct_fired.ts (90%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/avg_pct_no_data.ts (90%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/custom_eq_avg_bytes_fired.ts (91%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/documents_count_fired.ts (90%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/group_by_fired.ts (92%) rename x-pack/test_serverless/api_integration/test_suites/observability/{threshold_rule => custom_threshold_rule}/index.ts (93%) diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index 3ba31bb1ee1eb..97f4341168fd9 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -10,7 +10,7 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate'; -export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.threshold'; +export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export const ALERT_STATUS_ALL = 'all'; diff --git a/x-pack/plugins/observability/common/threshold_rule/color_palette.ts b/x-pack/plugins/observability/common/custom_threshold_rule/color_palette.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/color_palette.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/color_palette.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/constants.ts b/x-pack/plugins/observability/common/custom_threshold_rule/constants.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/constants.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/constants.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.test.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/bytes.test.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.test.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/bytes.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/bytes.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/datetime.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/datetime.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/datetime.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/high_precision.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/high_precision.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/high_precision.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/index.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/index.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/index.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/number.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/number.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/number.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/number.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/percent.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/percent.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/percent.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/snapshot_metric_formats.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/snapshot_metric_formats.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/snapshot_metric_formats.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/formatters/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/formatters/types.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/formatters/types.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/formatters/types.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.test.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.test.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.test.ts diff --git a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts similarity index 89% rename from x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts index 1ba0879fcf465..114f30fd85307 100644 --- a/x-pack/plugins/observability/common/threshold_rule/metric_value_formatter.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts @@ -10,7 +10,7 @@ import { createFormatter } from './formatters'; export const metricValueFormatter = (value: number | null, metric: string = '') => { const noDataValue = i18n.translate( - 'xpack.observability.threshold.rule.alerting.noDataFormattedValue', + 'xpack.observability.customThreshold.rule.alerting.noDataFormattedValue', { defaultMessage: '[NO DATA]', } diff --git a/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts similarity index 96% rename from x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts index d735e398e6661..66913385123c4 100644 --- a/x-pack/plugins/observability/common/threshold_rule/metrics_explorer.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metrics_explorer.ts @@ -7,19 +7,7 @@ import * as rt from 'io-ts'; import { xor } from 'lodash'; - -export const METRIC_EXPLORER_AGGREGATIONS = [ - 'avg', - 'max', - 'min', - 'cardinality', - 'rate', - 'count', - 'sum', - 'p95', - 'p99', - 'custom', -] as const; +import { METRIC_EXPLORER_AGGREGATIONS } from './constants'; export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; diff --git a/x-pack/plugins/observability/common/threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts similarity index 100% rename from x-pack/plugins/observability/common/threshold_rule/types.ts rename to x-pack/plugins/observability/common/custom_threshold_rule/types.ts diff --git a/x-pack/plugins/observability/public/components/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap rename to x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx similarity index 95% rename from x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx index 95367577ccb22..ecb31f4a49b4b 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx @@ -32,13 +32,13 @@ import { import { DataView } from '@kbn/data-views-plugin/common'; import type { TimeRange } from '@kbn/es-query'; import { useKibana } from '../../../utils/kibana_react'; -import { metricValueFormatter } from '../../../../common/threshold_rule/metric_value_formatter'; +import { metricValueFormatter } from '../../../../common/custom_threshold_rule/metric_value_formatter'; import { AlertSummaryField, TopAlert } from '../../..'; import { generateUniqueKey } from '../lib/generate_unique_key'; import { ExpressionChart } from './expression_chart'; import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; -import { Threshold } from './threshold'; +import { Threshold } from './custom_threshold'; import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options'; import { AlertParams, MetricExpression, MetricThresholdRuleTypeParams } from '../types'; @@ -98,7 +98,7 @@ export default function AlertDetailsAppSection({ setAlertSummaryFields([ { label: i18n.translate( - 'xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule', + 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule', { defaultMessage: 'Rule', } @@ -160,7 +160,7 @@ export default function AlertDetailsAppSection({ @@ -132,7 +132,7 @@ export function ErrorState() { diff --git a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx index c07e053374644..a8e64ac343a0f 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Color } from '../../../../../common/threshold_rule/color_palette'; -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Color } from '../../../../../common/custom_threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; import { shallow } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx similarity index 95% rename from x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx index 09bd7e21fdc10..e911eba6ad6c8 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/criterion_preview_chart/threshold_annotations.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/criterion_preview_chart/threshold_annotations.tsx @@ -7,8 +7,8 @@ import { AnnotationDomainType, LineAnnotation, RectAnnotation } from '@elastic/charts'; import { first, last } from 'lodash'; import React from 'react'; -import { Color, colorTransformer } from '../../../../../common/threshold_rule/color_palette'; -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Color, colorTransformer } from '../../../../../common/custom_threshold_rule/color_palette'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; interface ThresholdAnnotationsProps { threshold: number[]; diff --git a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx similarity index 98% rename from x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx index f7480aa1de513..f58894e7918d9 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.stories.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -13,7 +13,7 @@ import { Aggregators, Comparator, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../../../common'; import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx similarity index 92% rename from x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index ed3dc975b2d07..d41f5d5b0b85b 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -21,12 +21,12 @@ import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; import { DataViewBase } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/threshold_rule/metrics_explorer'; +import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/custom_threshold_rule/metrics_explorer'; import { Aggregators, CustomMetricAggTypes, CustomThresholdExpressionMetric, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../../types'; import { CustomMetrics, AggregationTypes, NormalizedFields } from './types'; @@ -148,7 +148,7 @@ export function CustomEquationEditor({ isDisabled={disableAdd} > @@ -160,7 +160,7 @@ export function CustomEquationEditor({ setCustomEqPopoverOpen(false)}>   setAggTypePopoverOpen(false)}> @@ -154,7 +157,7 @@ export function MetricRowWithAgg({ @@ -195,7 +198,7 @@ export function MetricRowWithAgg({ ) : ( { const renderComponent = (props: Partial = {}) => { diff --git a/x-pack/plugins/observability/public/components/threshold/components/threshold.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx similarity index 81% rename from x-pack/plugins/observability/public/components/threshold/components/threshold.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx index 28ae78cdccf27..0dd80826899b0 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/threshold.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_threshold.tsx @@ -10,7 +10,7 @@ import { Chart, Metric, Settings } from '@elastic/charts'; import { EuiIcon, EuiPanel, useEuiBackgroundColor } from '@elastic/eui'; import type { PartialTheme, Theme } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; export interface ChartProps { theme: PartialTheme; @@ -60,10 +60,13 @@ export function Threshold({ title, extra: ( - {i18n.translate('xpack.observability.threshold.rule.thresholdExtraTitle', { - values: { comparator, threshold: valueFormatter(threshold) }, - defaultMessage: `Alert when {comparator} {threshold}`, - })} + {i18n.translate( + 'xpack.observability.customThreshold.rule.thresholdExtraTitle', + { + values: { comparator, threshold: valueFormatter(threshold) }, + defaultMessage: `Alert when {comparator} {threshold}`, + } + )} ), color, diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx index 83c05b5694030..e9d6d2c726665 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.test.tsx @@ -14,7 +14,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; import { MetricExpression } from '../types'; import { ExpressionChart } from './expression_chart'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; const mockStartServices = mockCoreMock.createStart(); diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx similarity index 94% rename from x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx index 4172175a3b573..8dcb31dd96be5 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_chart.tsx @@ -27,12 +27,12 @@ import { useKibana } from '../../../utils/kibana_react'; import { MetricsExplorerAggregation, MetricsExplorerRow, -} from '../../../../common/threshold_rule/metrics_explorer'; -import { Color } from '../../../../common/threshold_rule/color_palette'; +} from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType, MetricsExplorerOptionsMetric, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; import { createFormatterForMetric } from '../helpers/create_formatter_for_metric'; import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; @@ -213,7 +213,7 @@ export function ExpressionChart({ {series.id !== 'ALL' ? ( @@ -221,7 +221,7 @@ export function ExpressionChart({ ) : ( diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx index 7a7536849c187..ed0c4c52f772c 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx similarity index 82% rename from x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index df8c7916ecba7..e508977658c0c 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -24,7 +24,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { DataViewBase } from '@kbn/es-query'; import { debounce } from 'lodash'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { AGGREGATION_TYPES, DerivedIndexPattern, MetricExpression } from '../types'; import { CustomEquationEditor } from './custom_equation'; import { CUSTOM_EQUATION, LABEL_HELP_MESSAGE, LABEL_LABEL } from '../i18n_strings'; @@ -33,7 +33,7 @@ import { decimalToPct, pctToDecimal } from '../helpers/corrected_percent_convert const customComparators = { ...builtInComparators, [Comparator.OUTSIDE_RANGE]: { - text: i18n.translate('xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel', { + text: i18n.translate('xpack.observability.customThreshold.rule.alertFlyout.outsideRangeLabel', { defaultMessage: 'Is not between', }), value: Comparator.OUTSIDE_RANGE, @@ -175,7 +175,7 @@ export const ExpressionRow: React.FC = (props) => { ({ id: 1, - title: i18n.translate('xpack.observability.threshold.rule.infrastructureDropdownTitle', { - defaultMessage: 'Infrastructure rules', - }), + title: i18n.translate( + 'xpack.observability.customThreshold.rule.infrastructureDropdownTitle', + { + defaultMessage: 'Infrastructure rules', + } + ), items: [ { 'data-test-subj': 'inventory-alerts-create-rule', - name: i18n.translate('xpack.observability.threshold.rule.createInventoryRuleButton', { - defaultMessage: 'Create inventory rule', - }), + name: i18n.translate( + 'xpack.observability.customThreshold.rule.createInventoryRuleButton', + { + defaultMessage: 'Create inventory rule', + } + ), onClick: () => { closePopover(); setVisibleFlyoutType('inventory'); @@ -66,15 +72,18 @@ export function MetricsAlertDropdown() { const metricsAlertsPanel = useMemo( () => ({ id: 2, - title: i18n.translate('xpack.observability.threshold.rule.metricsDropdownTitle', { + title: i18n.translate('xpack.observability.customThreshold.rule.metricsDropdownTitle', { defaultMessage: 'Metrics rules', }), items: [ { 'data-test-subj': 'metrics-threshold-alerts-create-rule', - name: i18n.translate('xpack.observability.threshold.rule.createThresholdRuleButton', { - defaultMessage: 'Create threshold rule', - }), + name: i18n.translate( + 'xpack.observability.customThreshold.rule.createThresholdRuleButton', + { + defaultMessage: 'Create threshold rule', + } + ), onClick: () => { closePopover(); setVisibleFlyoutType('threshold'); @@ -89,7 +98,7 @@ export function MetricsAlertDropdown() { const manageAlertsMenuItem = useMemo( () => ({ - name: i18n.translate('xpack.observability.threshold.rule.manageRules', { + name: i18n.translate('xpack.observability.customThreshold.rule.manageRules', { defaultMessage: 'Manage rules', }), icon: 'tableOfContents', @@ -105,7 +114,7 @@ export function MetricsAlertDropdown() { { 'data-test-subj': 'inventory-alerts-menu-option', name: i18n.translate( - 'xpack.observability.threshold.rule.infrastructureDropdownMenu', + 'xpack.observability.customThreshold.rule.infrastructureDropdownMenu', { defaultMessage: 'Infrastructure', } @@ -114,7 +123,7 @@ export function MetricsAlertDropdown() { }, { 'data-test-subj': 'metrics-threshold-alerts-menu-option', - name: i18n.translate('xpack.observability.threshold.rule.metricsDropdownMenu', { + name: i18n.translate('xpack.observability.customThreshold.rule.metricsDropdownMenu', { defaultMessage: 'Metrics', }), panel: 2, @@ -130,7 +139,7 @@ export function MetricsAlertDropdown() { [ { id: 0, - title: i18n.translate('xpack.observability.threshold.rule.alertDropdownTitle', { + title: i18n.translate('xpack.observability.customThreshold.rule.alertDropdownTitle', { defaultMessage: 'Alerts and rules', }), items: firstPanelMenuItems, @@ -152,7 +161,7 @@ export function MetricsAlertDropdown() { data-test-subj="thresholdRuleStructure-alerts-and-rules" > diff --git a/x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx index c7716c474b22c..c0e35c85580bc 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/series_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/series_chart.tsx @@ -14,12 +14,12 @@ import { AreaSeriesStyle, BarSeriesStyle, } from '@elastic/charts'; -import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; -import { Color, colorTransformer } from '../../../../common/threshold_rule/color_palette'; +import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color, colorTransformer } from '../../../../common/custom_threshold_rule/color_palette'; import { MetricsExplorerChartType, MetricsExplorerOptionsMetric, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; import { getMetricId } from '../helpers/get_metric_id'; import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_time_zone_setting'; diff --git a/x-pack/plugins/observability/public/components/threshold/components/triggers_actions_context.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/triggers_actions_context.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/triggers_actions_context.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/triggers_actions_context.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/components/validation.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/components/validation.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts diff --git a/x-pack/plugins/observability/public/components/threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx similarity index 76% rename from x-pack/plugins/observability/public/components/threshold/components/validation.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx index 4f5c818d7943d..d40a1f2852bcc 100644 --- a/x-pack/plugins/observability/public/components/threshold/components/validation.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -15,7 +15,7 @@ import { Comparator, CustomMetricExpressionParams, MetricExpressionParams, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; @@ -57,7 +57,7 @@ export function validateMetricThreshold({ if (!searchConfiguration || !searchConfiguration.index) { errors.searchConfiguration = [ i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.invalidSearchConfiguration', + 'xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration', { defaultMessage: 'Data view is required.', } @@ -74,9 +74,12 @@ export function validateMetricThreshold({ ); } catch (e) { errors.filterQuery = [ - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery', { - defaultMessage: 'Filter query is invalid.', - }), + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery', + { + defaultMessage: 'Filter query is invalid.', + } + ), ]; } } @@ -106,25 +109,34 @@ export function validateMetricThreshold({ }; if (!c.aggType) { errors[id].aggField.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired', { - defaultMessage: 'Aggregation is required.', - }) + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.aggregationRequired', + { + defaultMessage: 'Aggregation is required.', + } + ) ); } if (!c.threshold || !c.threshold.length) { errors[id].critical.threshold0.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) ); } if (c.warningThreshold && !c.warningThreshold.length) { errors[id].warning.threshold0.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) ); } @@ -145,7 +157,7 @@ export function validateMetricThreshold({ const key = i === 0 ? 'threshold0' : 'threshold1'; errors[id][type][key].push( i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired', + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdTypeRequired', { defaultMessage: 'Thresholds must contain a valid number.', } @@ -157,16 +169,19 @@ export function validateMetricThreshold({ if (comparator === Comparator.BETWEEN && (!threshold || threshold.length < 2)) { errors[id][type].threshold1.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired', { - defaultMessage: 'Threshold is required.', - }) + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.thresholdRequired', + { + defaultMessage: 'Threshold is required.', + } + ) ); } } if (!c.timeSize) { errors[id].timeWindowSize.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.timeRequred', { + i18n.translate('xpack.observability.customThreshold.rule.alertFlyout.error.timeRequred', { defaultMessage: 'Time size is Required.', }) ); @@ -174,16 +189,19 @@ export function validateMetricThreshold({ if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) { errors[id].metric.push( - i18n.translate('xpack.observability.threshold.rule.alertFlyout.error.metricRequired', { - defaultMessage: 'Metric is required.', - }) + i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.error.metricRequired', + { + defaultMessage: 'Metric is required.', + } + ) ); } if (isCustomMetricExpressionParams(c)) { if (!c.metrics || (c.metrics && c.metrics.length < 1)) { errors[id].metricsError = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.metricsError', + 'xpack.observability.customThreshold.rule.alertFlyout.error.metricsError', { defaultMessage: 'You must define at least 1 custom metric', } @@ -193,7 +211,7 @@ export function validateMetricThreshold({ const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {}; if (!metric.aggType) { customMetricErrors.aggType = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.metrics.aggTypeRequired', + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired', { defaultMessage: 'Aggregation is required', } @@ -201,7 +219,7 @@ export function validateMetricThreshold({ } if (metric.aggType !== 'count' && !metric.field) { customMetricErrors.field = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.metrics.fieldRequired', + 'xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired', { defaultMessage: 'Field is required', } @@ -222,7 +240,7 @@ export function validateMetricThreshold({ if (c.equation && c.equation.match(EQUATION_REGEX)) { errors[id].equation = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters', + 'xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters', { defaultMessage: 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', diff --git a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx similarity index 96% rename from x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx index e81e695f8cef9..accd48ca969a0 100644 --- a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx @@ -12,11 +12,11 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { queryClient } from '@kbn/osquery-plugin/public/query_client'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; -import { Comparator } from '../../../common/threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../common/threshold_rule/metrics_explorer'; +import { Comparator } from '../../../common/custom_threshold_rule/types'; +import { MetricsExplorerMetric } from '../../../common/custom_threshold_rule/metrics_explorer'; import { useKibana } from '../../utils/kibana_react'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; -import Expressions from './threshold_rule_expression'; +import Expressions from './custom_threshold_rule_expression'; jest.mock('../../utils/kibana_react'); diff --git a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx similarity index 92% rename from x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx index 1e25865147290..761cff54bc961 100644 --- a/x-pack/plugins/observability/public/components/threshold/threshold_rule_expression.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -36,7 +36,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../utils/kibana_react'; -import { Aggregators, Comparator } from '../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../common/utils/formatters/duration'; import { AlertContextMeta, AlertParams, MetricExpression } from './types'; import { ExpressionChart } from './components/expression_chart'; @@ -119,7 +119,7 @@ export default function Expressions(props: Props) { if (!timeFieldName) { setDataViewTimeFieldError( i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.dataViewError.noTimestamp', + 'xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp', { defaultMessage: 'The selected data view does not have a timestamp field, please select another data view.', @@ -382,7 +382,7 @@ export default function Expressions(props: Props) { } const placeHolder = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.searchBar.placeholder', + 'xpack.observability.customThreshold.rule.alertFlyout.searchBar.placeholder', { defaultMessage: 'Search for observability data… (e.g. host.name:host-1)', } @@ -393,7 +393,7 @@ export default function Expressions(props: Props) {
@@ -417,7 +417,7 @@ export default function Expressions(props: Props) {
@@ -448,7 +448,7 @@ export default function Expressions(props: Props) {
@@ -462,7 +462,7 @@ export default function Expressions(props: Props) {
@@ -514,18 +514,21 @@ export default function Expressions(props: Props) { onClick={addExpression} >
{redundantFilterGroupBy.join(', ')}, @@ -560,7 +563,7 @@ export default function Expressions(props: Props) { href={`${docLinks.links.observability.metricsThreshold}#filtering-and-grouping`} > {i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink', + 'xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink', { defaultMessage: 'the docs' } )} @@ -576,7 +579,7 @@ export default function Expressions(props: Props) { label={ <> {i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear', + 'xpack.observability.customThreshold.rule.alertFlyout.alertOnGroupDisappear', { defaultMessage: 'Alert me if a group stops reporting data', } @@ -585,7 +588,7 @@ export default function Expressions(props: Props) { content={ (disableNoData ? `${docCountNoDataDisabledHelpText} ` : '') + i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText', + 'xpack.observability.customThreshold.rule.alertFlyout.groupDisappearHelpText', { defaultMessage: 'Enable this to trigger the action if a previously detected group begins to report no results. This is not recommended for dynamically scaling infrastructures that may rapidly start and stop nodes automatically.', @@ -607,7 +610,7 @@ export default function Expressions(props: Props) { } const docCountNoDataDisabledHelpText = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText', + 'xpack.observability.customThreshold.rule.alertFlyout.docCountNoDataDisabledHelpText', { defaultMessage: '[This setting is not applicable to the Document Count aggregator.]', } diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts similarity index 92% rename from x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts index 17a3b3eee7726..16d0d54d825c1 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/calculate_domain.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/calculate_domain.ts @@ -6,8 +6,8 @@ */ import { min, max, sum, isNumber } from 'lodash'; -import { MetricsExplorerSeries } from '../../../../common/threshold_rule/metrics_explorer'; -import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; +import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; import { getMetricId } from './get_metric_id'; const getMin = (values: Array) => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.test.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.test.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/corrected_percent_convert.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/corrected_percent_convert.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts similarity index 76% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts index 7cb96af683573..1423413e98388 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metric.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metric.ts @@ -6,9 +6,9 @@ */ import numeral from '@elastic/numeral'; -import { InfraFormatterType } from '../../../../common/threshold_rule/types'; -import { createFormatter } from '../../../../common/threshold_rule/formatters'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; +import { createFormatter } from '../../../../common/custom_threshold_rule/formatters'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { metricToFormat } from './metric_to_format'; export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts similarity index 94% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts index 07ed09095a6aa..0da0e765d724a 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_formatter_for_metrics.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_formatter_for_metrics.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { createFormatterForMetric } from './create_formatter_for_metric'; describe('createFormatterForMetric()', () => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts similarity index 88% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts index f1fe34c377f13..70c4959aafc19 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { createMetricLabel } from './create_metric_label'; describe('createMetricLabel()', () => { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/create_metric_label.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/create_metric_label.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts index 969ade79e4dda..a60af79ac6e2d 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/get_metric_id.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/get_metric_id.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricsExplorerOptionsMetric } from '../../../../common/threshold_rule/types'; +import { MetricsExplorerOptionsMetric } from '../../../../common/custom_threshold_rule/types'; export const getMetricId = (metric: MetricsExplorerOptionsMetric, index: string | number) => { return `metric_${index}`; diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/kuery.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/kuery.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/kuery.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/kuery.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts similarity index 80% rename from x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts index 0ed004793b296..2a7d28b72b7c7 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/metric_to_format.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/metric_to_format.ts @@ -6,8 +6,8 @@ */ import { last } from 'lodash'; -import { InfraFormatterType } from '../../../../common/threshold_rule/types'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { InfraFormatterType } from '../../../../common/custom_threshold_rule/types'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; export const metricToFormat = (metric?: MetricsExplorerMetric) => { if (metric && metric.field) { diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts similarity index 69% rename from x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts index 914333439f426..bc840a206ac64 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/notifications.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/notifications.ts @@ -15,16 +15,19 @@ export const useSourceNotifier = () => { notifications.toasts.danger({ toastLifeTimeMs: 3000, title: i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle', + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateFailureTitle', { defaultMessage: 'Configuration update failed', } ), body: [ - i18n.translate('xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody', { - defaultMessage: - "We couldn't apply the changes to the Metrics configuration. Try again later.", - }), + i18n.translate( + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateFailureBody', + { + defaultMessage: + "We couldn't apply the changes to the Metrics configuration. Try again later.", + } + ), message, ] .filter(Boolean) @@ -36,7 +39,7 @@ export const useSourceNotifier = () => { notifications.toasts.success({ toastLifeTimeMs: 3000, title: i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle', + 'xpack.observability.customThreshold.rule.sourceConfiguration.updateSuccessTitle', { defaultMessage: 'Metrics settings successfully updated', } diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/runtime_types.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/runtime_types.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/runtime_types.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/runtime_types.ts diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts similarity index 89% rename from x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts index 45cbbad7d3e3a..3ab64f1aac0bb 100644 --- a/x-pack/plugins/observability/public/components/threshold/helpers/source_errors.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/helpers/source_errors.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; const missingHttpMessage = i18n.translate( - 'xpack.observability.threshold.rule.sourceConfiguration.missingHttp', + 'xpack.observability.customThreshold.rule.sourceConfiguration.missingHttp', { defaultMessage: 'Failed to load source: No HTTP client available.', } diff --git a/x-pack/plugins/observability/public/components/threshold/helpers/use_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/helpers/use_alert_prefill.ts rename to x-pack/plugins/observability/public/components/custom_threshold/helpers/use_alert_prefill.ts diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_time_zone_setting.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_time_zone_setting.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_time_zone_setting.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_time_zone_setting.ts diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_timefilter_time.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_timefilter_time.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_kibana_timefilter_time.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_kibana_timefilter_time.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts similarity index 91% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts index 86262d0f272c2..6314ddb20a115 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metric_threshold_alert_prefill.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metric_threshold_alert_prefill.ts @@ -7,7 +7,7 @@ import { isEqual } from 'lodash'; import { useState } from 'react'; -import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerMetric } from '../../../../common/custom_threshold_rule/metrics_explorer'; export interface MetricThresholdPrefillOptions { groupBy: string | string[] | undefined; diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts index 26924ff0d3d8e..ef810bc8857d9 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -8,8 +8,8 @@ import DateMath from '@kbn/datemath'; import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; -import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer'; -import { CustomThresholdExpressionMetric } from '../../../../common/threshold_rule/types'; +import { MetricExplorerCustomMetricAggregations } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { CustomThresholdExpressionMetric } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression, TimeRange } from '../types'; import { useMetricsExplorerData } from './use_metrics_explorer_data'; diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts index 2e01d72572fb8..59857990b906e 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_data.ts @@ -12,7 +12,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { MetricsExplorerResponse, metricsExplorerResponseRT, -} from '../../../../common/threshold_rule/metrics_explorer'; +} from '../../../../common/custom_threshold_rule/metrics_explorer'; import { MetricsExplorerOptions, diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.test.tsx diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts index a23bed69d9f43..7d734a961b04e 100644 --- a/x-pack/plugins/observability/public/components/threshold/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_metrics_explorer_options.ts @@ -12,8 +12,8 @@ import createContainer from 'constate'; import type { TimeRange } from '@kbn/es-query'; import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; -import { metricsExplorerMetricRT } from '../../../../common/threshold_rule/metrics_explorer'; -import { Color } from '../../../../common/threshold_rule/color_palette'; +import { metricsExplorerMetricRT } from '../../../../common/custom_threshold_rule/metrics_explorer'; +import { Color } from '../../../../common/custom_threshold_rule/color_palette'; import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime } from './use_kibana_timefilter_time'; diff --git a/x-pack/plugins/observability/public/components/threshold/hooks/use_tracked_promise.ts b/x-pack/plugins/observability/public/components/custom_threshold/hooks/use_tracked_promise.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/hooks/use_tracked_promise.ts rename to x-pack/plugins/observability/public/components/custom_threshold/hooks/use_tracked_promise.ts diff --git a/x-pack/plugins/observability/public/components/threshold/i18n_strings.ts b/x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts similarity index 65% rename from x-pack/plugins/observability/public/components/threshold/i18n_strings.ts rename to x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts index 0c480fbb9fb28..cca654da08a09 100644 --- a/x-pack/plugins/observability/public/components/threshold/i18n_strings.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/i18n_strings.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const EQUATION_HELP_MESSAGE = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.equationHelpMessage', { defaultMessage: 'Supports basic math equations, valid charaters are: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', @@ -16,32 +16,32 @@ export const EQUATION_HELP_MESSAGE = i18n.translate( ); export const LABEL_LABEL = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.labelLabel', { defaultMessage: 'Label (optional)' } ); export const LABEL_HELP_MESSAGE = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.labelHelpMessage', { defaultMessage: 'Custom label will show on the alert chart and in reason', } ); export const CUSTOM_EQUATION = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquation', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquation', { defaultMessage: 'Custom equation', } ); export const DELETE_LABEL = i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.deleteRowButton', { defaultMessage: 'Delete' } ); export const AGGREGATION_LABEL = (name: string) => i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel', + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel', { defaultMessage: 'Aggregation {name}', values: { name }, diff --git a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts index bafdf5f235467..f6b20963ccb00 100644 --- a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricExpression } from '../types'; import { generateUniqueKey } from './generate_unique_key'; diff --git a/x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/lib/generate_unique_key.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/generate_unique_key.ts diff --git a/x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts similarity index 91% rename from x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts rename to x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts index 5fc221afca80f..2a30136924420 100644 --- a/x-pack/plugins/observability/public/components/threshold/lib/transform_metrics_explorer_data.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/lib/transform_metrics_explorer_data.ts @@ -6,7 +6,7 @@ */ import { first } from 'lodash'; -import { MetricsExplorerResponse } from '../../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerResponse } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { MetricThresholdAlertParams, ExpressionChartSeries } from '../types'; export const transformMetricsExplorerData = ( diff --git a/x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts similarity index 93% rename from x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts rename to x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts index 478d27484df0d..810ff3c1f5983 100644 --- a/x-pack/plugins/observability/public/components/threshold/mocks/metric_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/metric_threshold_rule.ts @@ -6,7 +6,7 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { MetricThresholdAlert, MetricThresholdRule } from '../components/alert_details_app_section'; @@ -159,18 +159,18 @@ export const buildMetricThresholdAlert = ( alertOnGroupDisappear: true, }, 'kibana.alert.evaluation.values': [2500, 5], - 'kibana.alert.rule.category': 'Metric threshold', + 'kibana.alert.rule.category': 'Custom threshold (BETA)', 'kibana.alert.rule.consumer': 'alerts', 'kibana.alert.rule.execution.uuid': '62dd07ef-ead9-4b1f-a415-7c83d03925f7', 'kibana.alert.rule.name': 'One condition', - 'kibana.alert.rule.producer': 'infrastructure', - 'kibana.alert.rule.rule_type_id': 'metrics.alert.threshold', + 'kibana.alert.rule.producer': 'observability', + 'kibana.alert.rule.rule_type_id': 'observability.rules.custom_threshold', 'kibana.alert.rule.uuid': '3a1ed8c0-c1a8-11ed-9249-ed6d75986bdc', 'kibana.space_ids': ['default'], 'kibana.alert.rule.tags': [], '@timestamp': '2023-03-28T14:40:00.000Z', 'kibana.alert.reason': 'system.cpu.user.pct reported no data in the last 1m for ', - 'kibana.alert.action_group': 'metrics.threshold.nodata', + 'kibana.alert.action_group': 'custom_threshold.nodata', tags: [], 'kibana.alert.duration.us': 248391946000, 'kibana.alert.time_range': { diff --git a/x-pack/plugins/observability/public/components/threshold/rule_data_formatters.ts b/x-pack/plugins/observability/public/components/custom_threshold/rule_data_formatters.ts similarity index 100% rename from x-pack/plugins/observability/public/components/threshold/rule_data_formatters.ts rename to x-pack/plugins/observability/public/components/custom_threshold/rule_data_formatters.ts diff --git a/x-pack/plugins/observability/public/components/threshold/types.ts b/x-pack/plugins/observability/public/components/custom_threshold/types.ts similarity index 97% rename from x-pack/plugins/observability/public/components/threshold/types.ts rename to x-pack/plugins/observability/public/components/custom_threshold/types.ts index 6336c3f292677..d0b173b4d7c34 100644 --- a/x-pack/plugins/observability/public/components/threshold/types.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/types.ts @@ -25,7 +25,7 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { TimeUnitChar } from '../../../common/utils/formatters'; -import { MetricsExplorerSeries } from '../../../common/threshold_rule/metrics_explorer'; +import { MetricsExplorerSeries } from '../../../common/custom_threshold_rule/metrics_explorer'; import { Comparator, CustomMetricExpressionParams, @@ -33,7 +33,7 @@ import { MetricsSourceStatus, NonCountMetricExpressionParams, SnapshotCustomMetricInput, -} from '../../../common/threshold_rule/types'; +} from '../../../common/custom_threshold_rule/types'; import { ObservabilityPublicStart } from '../../plugin'; import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options'; diff --git a/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx b/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx index 86a9300e98aa0..2325434a08234 100644 --- a/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx +++ b/x-pack/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx @@ -14,7 +14,7 @@ import { } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; -import { InfraClientStartDeps, RendererFunction } from '../threshold/types'; +import { InfraClientStartDeps, RendererFunction } from '../custom_threshold/types'; interface WithKueryAutocompletionLifecycleProps { kibana: KibanaReactContextValue; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index c06bd84e02c77..e7195dfad76ac 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -17,8 +17,8 @@ import { SLO_BURN_RATE_RULE_TYPE_ID, } from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; -import { validateMetricThreshold } from '../components/threshold/components/validation'; -import { formatReason } from '../components/threshold/rule_data_formatters'; +import { validateMetricThreshold } from '../components/custom_threshold/components/validation'; +import { formatReason } from '../components/custom_threshold/rule_data_formatters'; const sloBurnRateDefaultActionMessage = i18n.translate( 'xpack.observability.slo.rules.burnRate.defaultActionMessage', @@ -54,7 +54,7 @@ const sloBurnRateDefaultRecoveryMessage = i18n.translate( ); const thresholdDefaultActionMessage = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage', + 'xpack.observability.customThreshold.rule.alerting.threshold.defaultActionMessage', { defaultMessage: `\\{\\{context.reason\\}\\} @@ -65,7 +65,7 @@ const thresholdDefaultActionMessage = i18n.translate( } ); const thresholdDefaultRecoveryMessage = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.defaultRecoveryMessage', + 'xpack.observability.customThreshold.rule.alerting.threshold.defaultRecoveryMessage', { defaultMessage: `\\{\\{rule.name\\}\\} has recovered. @@ -106,7 +106,7 @@ export const registerObservabilityRuleTypes = ( observabilityRuleTypeRegistry.register({ id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, description: i18n.translate( - 'xpack.observability.threshold.rule.alertFlyout.alertDescription', + 'xpack.observability.customThreshold.rule.alertFlyout.alertDescription', { defaultMessage: 'Alert when any Observability data type reaches or exceeds a given value.', @@ -116,14 +116,16 @@ export const registerObservabilityRuleTypes = ( documentationUrl(docLinks) { return `${docLinks.links.observability.threshold}`; }, - ruleParamsExpression: lazy(() => import('../components/threshold/threshold_rule_expression')), + ruleParamsExpression: lazy( + () => import('../components/custom_threshold/custom_threshold_rule_expression') + ), validate: validateMetricThreshold, defaultActionMessage: thresholdDefaultActionMessage, defaultRecoveryMessage: thresholdDefaultRecoveryMessage, requiresAppContext: false, format: formatReason, alertDetailsAppSection: lazy( - () => import('../components/threshold/components/alert_details_app_section') + () => import('../components/custom_threshold/components/alert_details_app_section') ), }); } diff --git a/x-pack/plugins/observability/public/utils/metrics_explorer.ts b/x-pack/plugins/observability/public/utils/metrics_explorer.ts index 3994a1c4ee69b..136df77131811 100644 --- a/x-pack/plugins/observability/public/utils/metrics_explorer.ts +++ b/x-pack/plugins/observability/public/utils/metrics_explorer.ts @@ -8,7 +8,7 @@ import { MetricsExplorerResponse, MetricsExplorerSeries, -} from '../../common/threshold_rule/metrics_explorer'; +} from '../../common/custom_threshold_rule/metrics_explorer'; import { MetricsExplorerChartOptions, MetricsExplorerChartType, @@ -16,7 +16,7 @@ import { MetricsExplorerTimeOptions, MetricsExplorerTimestampsRT, MetricsExplorerYAxisMode, -} from '../components/threshold/hooks/use_metrics_explorer_options'; +} from '../components/custom_threshold/hooks/use_metrics_explorer_options'; export const options: MetricsExplorerOptions = { limit: 3, diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index d5d5604001aa9..e0444fe34f861 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -50,7 +50,7 @@ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), }), }), - thresholdRule: schema.object({ + customThresholdRule: schema.object({ groupByPageSize: schema.number({ defaultValue: 10_000 }), }), enabled: schema.boolean({ defaultValue: true }), diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts similarity index 99% rename from x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index 1cf093ab91eee..4a4640e6e1f2f 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -18,7 +18,7 @@ import { FIRED_ACTIONS, MetricThresholdAlertContext, NO_DATA_ACTIONS, -} from './threshold_executor'; +} from './custom_threshold_executor'; import { Evaluation } from './lib/evaluate_rule'; import type { LogMeta, Logger } from '@kbn/logging'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; @@ -27,7 +27,7 @@ import { Comparator, CountMetricExpressionParams, NonCountMetricExpressionParams, -} from '../../../../common/threshold_rule/types'; +} from '../../../../common/custom_threshold_rule/types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); @@ -1888,7 +1888,7 @@ const mockLibs: any = { }, logger, config: { - thresholdRule: { + customThresholdRule: { groupByPageSize: 10_000, }, }, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts similarity index 94% rename from x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 5ce2e40a1d023..bff471aaea2ec 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/threshold_executor.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -19,8 +19,8 @@ import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; import { IBasePath, Logger } from '@kbn/core/server'; import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server'; import { AlertsLocatorParams, getAlertUrl, TimeUnitChar } from '../../../../common'; -import { createFormatter } from '../../../../common/threshold_rule/formatters'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { createFormatter } from '../../../../common/custom_threshold_rule/formatters'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { ObservabilityConfig } from '../../..'; import { AlertStates, searchConfigurationSchema } from './types'; @@ -62,8 +62,8 @@ export interface MetricThresholdAlertContext extends Record { value?: Array | null; } -export const FIRED_ACTIONS_ID = 'threshold.fired'; -export const NO_DATA_ACTIONS_ID = 'threshold.nodata'; +export const FIRED_ACTIONS_ID = 'custom_threshold.fired'; +export const NO_DATA_ACTIONS_ID = 'custom_threshold.nodata'; type MetricThresholdActionGroup = | typeof FIRED_ACTIONS_ID @@ -158,7 +158,7 @@ export const createMetricThresholdExecutor = ({ // For backwards-compatibility, interpret undefined alertOnGroupDisappear as true const alertOnGroupDisappear = _alertOnGroupDisappear !== false; - const compositeSize = config.thresholdRule.groupByPageSize; + const compositeSize = config.customThresholdRule.groupByPageSize; const queryIsSame = isEqual( state.searchConfiguration?.query.query, params.searchConfiguration.query.query @@ -368,17 +368,20 @@ export const createMetricThresholdExecutor = ({ }; export const FIRED_ACTIONS = { - id: 'threshold.fired', - name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.fired', { + id: 'custom_threshold.fired', + name: i18n.translate('xpack.observability.customThreshold.rule.alerting.custom_threshold.fired', { defaultMessage: 'Alert', }), }; export const NO_DATA_ACTIONS = { - id: 'threshold.nodata', - name: i18n.translate('xpack.observability.threshold.rule.alerting.threshold.nodata', { - defaultMessage: 'No Data', - }), + id: 'custom_threshold.nodata', + name: i18n.translate( + 'xpack.observability.customThreshold.rule.alerting.custom_threshold.nodata', + { + defaultMessage: 'No Data', + } + ), }; const formatAlertResult = ( @@ -393,7 +396,7 @@ const formatAlertResult = ( ) => { const { metric, currentValue, threshold, comparator } = alertResult; const noDataValue = i18n.translate( - 'xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue', + 'xpack.observability.customThreshold.rule.alerting.threshold.noDataFormattedValue', { defaultMessage: '[NO DATA]' } ); diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts similarity index 96% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts index 4b8c47a1af866..7651130601b6c 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/check_missing_group.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts @@ -8,7 +8,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { isString, get, identity } from 'lodash'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import type { BucketKey } from './get_data'; import { calculateCurrentTimeframe, createBaseFilters } from './metric_query'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/convert_strings_to_missing_groups_record.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/convert_strings_to_missing_groups_record.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/convert_strings_to_missing_groups_record.ts diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts index 0af245df1a102..6300bfac703f3 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_bucket_selector.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_bucket_selector.ts @@ -9,7 +9,7 @@ import { Aggregators, Comparator, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { createConditionScript } from './create_condition_script'; import { createLastPeriod } from './wrap_in_period'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts similarity index 92% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts index 2355b2d301065..ad4aaa980aa63 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_condition_script.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_condition_script.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Comparator } from '../../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; export const createConditionScript = (threshold: number[], comparator: Comparator) => { if (comparator === Comparator.BETWEEN && threshold.length === 2) { diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts index a52ad0247475c..93eecd37c1c5d 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_custom_metrics_aggregations.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts @@ -7,7 +7,7 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; -import { CustomThresholdExpressionMetric } from '../../../../../common/threshold_rule/types'; +import { CustomThresholdExpressionMetric } from '../../../../../common/custom_threshold_rule/types'; import { MetricsExplorerCustomMetric } from './metrics_explorer'; const isMetricExpressionCustomMetric = ( diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts similarity index 87% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts index d96dbe49f5a88..73db4a3e747ee 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_percentile_aggregation.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_percentile_aggregation.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; export const createPercentileAggregation = ( type: Aggregators.P95 | Aggregators.P99, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_rate_aggregation.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts index 06b1554706a93..c48ce1d9ab50d 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; import moment from 'moment'; import { createTimerange } from './create_timerange'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts similarity index 92% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts index 446299c4ba92b..75f4dda7ff8d6 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/create_timerange.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { Aggregators } from '../../../../../common/threshold_rule/types'; +import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; export const createTimerange = ( interval: number, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts similarity index 96% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 31b86eb73beea..66007de19b622 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -8,10 +8,10 @@ import moment from 'moment'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import { isCustom } from './metric_expression_params'; import { AdditionalContext, getIntervalInSeconds } from '../utils'; -import { SearchConfigurationType } from '../threshold_executor'; +import { SearchConfigurationType } from '../custom_threshold_executor'; import { CUSTOM_EQUATION_I18N, DOCUMENT_COUNT_I18N } from '../messages'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts similarity index 99% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts index 9d5db3a14727b..d5a09dbf567a3 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/get_data.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/get_data.ts @@ -13,7 +13,7 @@ import { Aggregators, Comparator, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import { CONTAINER_ID, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts similarity index 93% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts index c81b43c9baeb6..278f33dadafe9 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_expression_params.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_expression_params.ts @@ -9,7 +9,7 @@ import { CustomMetricExpressionParams, MetricExpressionParams, NonCountMetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; export const isNotCountOrCustom = ( metricExpressionParams: MetricExpressionParams diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts index 2aed703e9c3a1..cdfe0c66c0ab9 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts @@ -9,7 +9,7 @@ import { Comparator, Aggregators, MetricExpressionParams, -} from '../../../../../common/threshold_rule/types'; +} from '../../../../../common/custom_threshold_rule/types'; import moment from 'moment'; import { getElasticsearchMetricQuery } from './metric_query'; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts similarity index 98% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index 3eac288648093..66868ad97dfc9 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -6,7 +6,10 @@ */ import moment from 'moment'; -import { Aggregators, MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { + Aggregators, + MetricExpressionParams, +} from '../../../../../common/custom_threshold_rule/types'; import { isCustom, isNotCountOrCustom } from './metric_expression_params'; import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; import { diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts similarity index 94% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts index 2de1a21c3cd48..e72974828b027 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/metrics_explorer.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metrics_explorer.ts @@ -6,20 +6,8 @@ */ import * as rt from 'io-ts'; -import { metricsExplorerCustomMetricAggregationRT } from '../../../../../common/threshold_rule/metrics_explorer'; - -export const METRIC_EXPLORER_AGGREGATIONS = [ - 'avg', - 'max', - 'min', - 'cardinality', - 'rate', - 'count', - 'sum', - 'p95', - 'p99', - 'custom', -] as const; +import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../../common/custom_threshold_rule/constants'; +import { metricsExplorerCustomMetricAggregationRT } from '../../../../../common/custom_threshold_rule/metrics_explorer'; type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts similarity index 93% rename from x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts index d313e5efd9fcd..a76c45e4e4583 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/lib/wrap_in_period.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/wrap_in_period.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { MetricExpressionParams } from '../../../../../common/threshold_rule/types'; +import { MetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; export const createLastPeriod = ( lastPeriodEnd: number, diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts similarity index 73% rename from x-pack/plugins/observability/server/lib/rules/threshold/messages.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts index ac0f1ef8b98c4..80ca06c24e59f 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/messages.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts @@ -6,19 +6,19 @@ */ import { i18n } from '@kbn/i18n'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../../common'; import { UNGROUPED_FACTORY_KEY } from './utils'; export const DOCUMENT_COUNT_I18N = i18n.translate( - 'xpack.observability.threshold.rule.threshold.documentCount', + 'xpack.observability.customThreshold.rule.threshold.documentCount', { defaultMessage: 'Document count', } ); export const CUSTOM_EQUATION_I18N = i18n.translate( - 'xpack.observability.threshold.rule.threshold.customEquation', + 'xpack.observability.customThreshold.rule.threshold.customEquation', { defaultMessage: 'Custom equation', } @@ -32,17 +32,23 @@ const recoveredComparatorToI18n = ( threshold: number[], currentValue: number ) => { - const belowText = i18n.translate('xpack.observability.threshold.rule.threshold.belowRecovery', { - defaultMessage: 'below', - }); - const aboveText = i18n.translate('xpack.observability.threshold.rule.threshold.aboveRecovery', { - defaultMessage: 'above', - }); + const belowText = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.belowRecovery', + { + defaultMessage: 'below', + } + ); + const aboveText = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.aboveRecovery', + { + defaultMessage: 'above', + } + ); switch (comparator) { case Comparator.BETWEEN: return currentValue < threshold[0] ? belowText : aboveText; case Comparator.OUTSIDE_RANGE: - return i18n.translate('xpack.observability.threshold.rule.threshold.betweenRecovery', { + return i18n.translate('xpack.observability.customThreshold.rule.threshold.betweenRecovery', { defaultMessage: 'between', }); case Comparator.GT: @@ -56,7 +62,7 @@ const recoveredComparatorToI18n = ( const thresholdToI18n = ([a, b]: Array) => { if (typeof b === 'undefined') return a; - return i18n.translate('xpack.observability.threshold.rule.threshold.thresholdRange', { + return i18n.translate('xpack.observability.customThreshold.rule.threshold.thresholdRange', { defaultMessage: '{a} and {b}', values: { a, b }, }); @@ -73,7 +79,7 @@ export const buildFiredAlertReason: (alertResult: { timeSize: number; timeUnit: TimeUnitChar; }) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) => - i18n.translate('xpack.observability.threshold.rule.threshold.firedAlertReason', { + i18n.translate('xpack.observability.customThreshold.rule.threshold.firedAlertReason', { defaultMessage: '{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.', values: { @@ -94,7 +100,7 @@ export const buildRecoveredAlertReason: (alertResult: { threshold: Array; currentValue: number | string; }) => string = ({ group, metric, comparator, threshold, currentValue }) => - i18n.translate('xpack.observability.threshold.rule.threshold.recoveredAlertReason', { + i18n.translate('xpack.observability.customThreshold.rule.threshold.recoveredAlertReason', { defaultMessage: '{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue}) for {group}', values: { @@ -116,7 +122,7 @@ export const buildNoDataAlertReason: (alertResult: { timeSize: number; timeUnit: string; }) => string = ({ group, metric, timeSize, timeUnit }) => - i18n.translate('xpack.observability.threshold.rule.threshold.noDataAlertReason', { + i18n.translate('xpack.observability.customThreshold.rule.threshold.noDataAlertReason', { defaultMessage: '{metric} reported no data in the last {interval}{group}', values: { metric, @@ -126,7 +132,7 @@ export const buildNoDataAlertReason: (alertResult: { }); export const buildErrorAlertReason = (metric: string) => - i18n.translate('xpack.observability.threshold.rule.threshold.errorAlertReason', { + i18n.translate('xpack.observability.customThreshold.rule.threshold.errorAlertReason', { defaultMessage: 'Elasticsearch failed when attempting to query data for {metric}', values: { metric, @@ -134,14 +140,14 @@ export const buildErrorAlertReason = (metric: string) => }); export const groupByKeysActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.groupByKeysActionVariableDescription', + 'xpack.observability.customThreshold.rule.groupByKeysActionVariableDescription', { defaultMessage: 'The object containing groups that are reporting data', } ); export const alertDetailUrlActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription', + 'xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription', { defaultMessage: 'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.', @@ -149,70 +155,70 @@ export const alertDetailUrlActionVariableDescription = i18n.translate( ); export const reasonActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.reasonActionVariableDescription', + 'xpack.observability.customThreshold.rule.reasonActionVariableDescription', { defaultMessage: 'A concise description of the reason for the alert', } ); export const timestampActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.timestampDescription', + 'xpack.observability.customThreshold.rule.timestampDescription', { defaultMessage: 'A timestamp of when the alert was detected.', } ); export const valueActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.valueActionVariableDescription', + 'xpack.observability.customThreshold.rule.valueActionVariableDescription', { defaultMessage: 'List of the condition values.', } ); export const viewInAppUrlActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription', + 'xpack.observability.customThreshold.rule.viewInAppUrlActionVariableDescription', { defaultMessage: 'Link to the alert source', } ); export const cloudActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.cloudActionVariableDescription', + 'xpack.observability.customThreshold.rule.cloudActionVariableDescription', { defaultMessage: 'The cloud object defined by ECS if available in the source.', } ); export const hostActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.hostActionVariableDescription', + 'xpack.observability.customThreshold.rule.hostActionVariableDescription', { defaultMessage: 'The host object defined by ECS if available in the source.', } ); export const containerActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.containerActionVariableDescription', + 'xpack.observability.customThreshold.rule.containerActionVariableDescription', { defaultMessage: 'The container object defined by ECS if available in the source.', } ); export const orchestratorActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.orchestratorActionVariableDescription', + 'xpack.observability.customThreshold.rule.orchestratorActionVariableDescription', { defaultMessage: 'The orchestrator object defined by ECS if available in the source.', } ); export const labelsActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.labelsActionVariableDescription', + 'xpack.observability.customThreshold.rule.labelsActionVariableDescription', { defaultMessage: 'List of labels associated with the entity where this alert triggered.', } ); export const tagsActionVariableDescription = i18n.translate( - 'xpack.observability.threshold.rule.tagsActionVariableDescription', + 'xpack.observability.customThreshold.rule.tagsActionVariableDescription', { defaultMessage: 'List of tags associated with the entity where this alert triggered.', } diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts similarity index 97% rename from x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index 791b6ff680992..3b1b01e4d2268 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/register_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -21,7 +21,7 @@ import { observabilityFeatureId, observabilityPaths, } from '../../../../common'; -import { Comparator } from '../../../../common/threshold_rule/types'; +import { Comparator } from '../../../../common/custom_threshold_rule/types'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; @@ -43,9 +43,9 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS, NO_DATA_ACTIONS, -} from './threshold_executor'; +} from './custom_threshold_executor'; import { ObservabilityConfig } from '../../..'; -import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/threshold_rule/constants'; +import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/custom_threshold_rule/constants'; export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts = { context: THRESHOLD_RULE_REGISTRATION_CONTEXT, @@ -121,7 +121,7 @@ export function thresholdRuleType( return { id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, name: i18n.translate('xpack.observability.threshold.ruleName', { - defaultMessage: 'Threshold (Technical Preview)', + defaultMessage: 'Custom threshold (BETA)', }), validate: { params: schema.object( diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/types.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts similarity index 96% rename from x-pack/plugins/observability/server/lib/rules/threshold/types.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts index 40115fdeb9c80..e66042bcb648d 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/types.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/types.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import * as rt from 'io-ts'; import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold'; import { validateKQLStringFilter } from './utils'; -import { Aggregators, Comparator } from '../../../../common/threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { TimeUnitChar } from '../../../../common'; export enum InfraRuleType { diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.test.ts similarity index 100% rename from x-pack/plugins/observability/server/lib/rules/threshold/utils.test.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.test.ts diff --git a/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts similarity index 99% rename from x-pack/plugins/observability/server/lib/rules/threshold/utils.ts rename to x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts index 7c85c7ab98630..854b7cc497502 100644 --- a/x-pack/plugins/observability/server/lib/rules/threshold/utils.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts @@ -52,7 +52,7 @@ export const validateKQLStringFilter = (value: string) => { try { kbnBuildEsQuery(undefined, [{ query: value, language: 'kuery' }], []); } catch (e) { - return i18n.translate('xpack.observability.threshold.rule.schema.invalidFilterQuery', { + return i18n.translate('xpack.observability.customThreshold.rule.schema.invalidFilterQuery', { defaultMessage: 'filterQuery must be a valid KQL filter', }); } diff --git a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts index 388ede407819f..0e1abfc1037d7 100644 --- a/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts +++ b/x-pack/plugins/observability/server/lib/rules/register_rule_types.ts @@ -22,7 +22,7 @@ import { THRESHOLD_RULE_REGISTRATION_CONTEXT, } from '../../common/constants'; import { sloBurnRateRuleType } from './slo_burn_rate'; -import { thresholdRuleType } from './threshold/register_threshold_rule_type'; +import { thresholdRuleType } from './custom_threshold/register_custom_threshold_rule_type'; import { sloRuleFieldMap } from './slo_burn_rate/field_map'; export function registerRuleTypes( diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 49fa6be17eb96..fb4219e6052f4 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -27128,18 +27128,6 @@ "xpack.observability.slo.update.errorNotification": "Un problème est survenu lors de la mise à jour de {name}", "xpack.observability.slo.update.successNotification": "Mise à jour réussie de {name}", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "Activez les paramètres de régulation dans les configurations du moniteur Synthetics. Notez qu’il est possible que la régulation ne soit pas toujours disponible pour vos moniteurs, même si le paramètre est actif. Destiné à un usage interne uniquement. {link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "Dernière {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} many {ces champs} other {ces champs}}. Pour en savoir plus, consultez notre {filteringAndGroupingLink}.", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "Agrégation {name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "Filtre KQL {name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "Dernière {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "Dernières {lookback} {timeLabel} de données pour {id}", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch a échoué lors de l'interrogation des données pour {metric}", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "{metric} est {currentValue} dans les dernières {duration}{group}. Alerte lorsque {comparator} {threshold}.", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "{metric} n'a signalé aucune donnée dans les dernières {interval} {group}", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "{metric} est maintenant {comparator} un seuil de {threshold} (la valeur actuelle est {currentValue}) pour {group}", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a} et {b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "Alerte lorsque {comparator} {threshold}.", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " et inférieur à {bad}", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd} %)", @@ -27586,81 +27574,6 @@ "xpack.observability.statusVisualization.ux.link": "Ajouter des données", "xpack.observability.statusVisualization.ux.title": "Expérience utilisateur", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "Activer la régulation Synthetics (Expérimentale)", - "xpack.observability.threshold.rule..charts.errorMessage": "Oups, un problème est survenu", - "xpack.observability.threshold.rule..charts.noDataMessage": "Aucune donnée graphique disponible", - "xpack.observability.threshold.rule..timeLabels.days": "jours", - "xpack.observability.threshold.rule..timeLabels.hours": "heures", - "xpack.observability.threshold.rule..timeLabels.minutes": "minutes", - "xpack.observability.threshold.rule..timeLabels.seconds": "secondes", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "Règle", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "Seuil dépassé", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "Lien vers l’affichage de résolution des problèmes d’alerte pour voir plus de contextes et de détails. La chaîne sera vide si server.publicBaseUrl n'est pas configuré.", - "xpack.observability.threshold.rule.alertDropdownTitle": "Alertes et règles", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "Ajouter une condition", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "Moyenne", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "Cardinalité", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "Compte du document", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "Max", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "Min", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "95e centile", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "99e centile", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "Taux", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "Somme", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "Alerter quand un type de données Observability atteint ou dépasse une valeur donnée.", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "Me prévenir si un groupe cesse de signaler les données", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "les documents", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "Créer une alerte pour chaque valeur unique. Par exemple : \"host.id\" ou \"cloud.region\".", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "Regrouper les alertes par (facultatif)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "Équation personnalisée", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "Ajouter une agrégation/un champ", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "Supprimer", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "Prend en charge les expressions mathématiques de base", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "L'étiquette personnalisée s'affichera sur le graphique d'alerte et dans le titre de raison/d'alerte", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "Étiquette (facultatif)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[Ce paramètre n’est pas applicable à l’agrégateur du nombre de documents.]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "L'agrégation est requise.", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "Le champ d'équation prend en charge uniquement les caractères suivants : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "La requête de filtre n'est pas valide.", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "L'indicateur est requis.", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "Le seuil est requis.", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "Les seuils doivent contenir un nombre valide.", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "La taille de temps est requise.", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "Activez cette option pour déclencher l’action si un groupe précédemment détecté cesse de signaler des résultats. Ce n’est pas recommandé pour les infrastructures à montée en charge dynamique qui peuvent rapidement lancer ou stopper des nœuds automatiquement.", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "N'est pas entre", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "Retirer la condition", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[AUCUNE DONNÉE]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\} est à l'état \\{\\{context.alertState\\}\\}\n\n Raison :\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "Alerte", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "Aucune donnée", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[AUCUNE DONNÉE]", - "xpack.observability.threshold.rule.alertsButton": "Alertes et règles", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "Objet cloud défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.containerActionVariableDescription": "Objet conteneur défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.createInventoryRuleButton": "Créer une règle d'inventaire", - "xpack.observability.threshold.rule.createThresholdRuleButton": "Créer une règle de seuil", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "Objet contenant les groupes qui fournissent les données", - "xpack.observability.threshold.rule.hostActionVariableDescription": "Objet hôte défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "Infrastructure", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "Règles d'infrastructure", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "Liste d'étiquettes associées avec l'entité sur laquelle l'alerte s'est déclenchée.", - "xpack.observability.threshold.rule.manageRules": "Gérer les règles", - "xpack.observability.threshold.rule.metricsDropdownMenu": "Indicateurs", - "xpack.observability.threshold.rule.metricsDropdownTitle": "Règles d'indicateurs", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "Objet orchestrateur défini par ECS s'il est disponible dans la source.", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "Une description concise de la raison du signalement", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "Échec de chargement de la source : Aucun client HTTP disponible.", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "Nous n'avons pas pu appliquer les modifications à la configuration des indicateurs. Réessayez plus tard.", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "La mise à jour de la configuration a échoué", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "Les paramètres d'indicateurs ont bien été mis à jour", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "Liste de balises associées avec l'entité sur laquelle l'alerte s'est déclenchée.", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "supérieur à", - "xpack.observability.threshold.rule.threshold.belowRecovery": "inférieur à", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "entre", - "xpack.observability.threshold.rule.threshold.customEquation": "Équation personnalisée", - "xpack.observability.threshold.rule.threshold.documentCount": "Nombre de documents", - "xpack.observability.threshold.rule.timestampDescription": "Horodatage du moment où l'alerte a été détectée.", - "xpack.observability.threshold.rule.valueActionVariableDescription": "Valeur de l'indicateur dans la condition spécifiée. Utilisation : (ctx.value.condition0, ctx.value.condition1, etc...).", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "Lien vers la source de l’alerte", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "Graphique par", "xpack.observability.threshold.ruleExplorer.groupByLabel": "Tout", "xpack.observability.threshold.ruleName": "Seuil (Version d'évaluation technique)", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 00df50d572e22..9c1bd25593a47 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -27128,18 +27128,6 @@ "xpack.observability.slo.update.errorNotification": "{name}の更新中にエラーが発生しました", "xpack.observability.slo.update.successNotification": "正常に{name}を更新しました", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "シンセティック監視構成で調整設定を有効にします。設定が有効でも、モニターで調整を使用できない場合があります。内部使用専用です。{link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "フィルタークエリには{groupCount, plural, other {これらのフィールド}}に対する一致が含まれているため、このルールによって、想定を下回る{matchedGroups}に関するアラートが発行される場合があります。詳細については、{filteringAndGroupingLink}を参照してください。", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "アグリゲーション{name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQLフィルター{name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id}のデータの最後の{lookback} {timeLabel}", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "{metric}のデータのクエリを試行しているときに、Elasticsearchが失敗しました", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "{metric}は最後の{duration}{group}の{currentValue}です。{comparator} {threshold}のときにアラートを通知します。", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "{metric}は最後の{interval}{group}でデータがないことを報告しました", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "{metric} が {comparator} に、{group} が {threshold} のしきい値(現在の値は {currentValue})になりました", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a}および{b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "{comparator} {threshold}のときにアラートを通知", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " {bad}未満", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd}({ranksInd}%)", @@ -27586,81 +27574,6 @@ "xpack.observability.statusVisualization.ux.link": "データの追加", "xpack.observability.statusVisualization.ux.title": "ユーザーエクスペリエンス", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "シンセティック調整を有効にする(実験)", - "xpack.observability.threshold.rule..charts.errorMessage": "問題が発生しました", - "xpack.observability.threshold.rule..charts.noDataMessage": "グラフデータがありません", - "xpack.observability.threshold.rule..timeLabels.days": "日", - "xpack.observability.threshold.rule..timeLabels.hours": "時間", - "xpack.observability.threshold.rule..timeLabels.minutes": "分", - "xpack.observability.threshold.rule..timeLabels.seconds": "秒", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "ルール", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "しきい値を超えました", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "アラートトラブルシューティングビューにリンクして、さらに詳しい状況や詳細を確認できます。server.publicBaseUrlが構成されていない場合は、空の文字列になります。", - "xpack.observability.threshold.rule.alertDropdownTitle": "アラートとルール", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "条件を追加", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "平均", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "基数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "ドキュメントカウント", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "最高", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "最低", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "95パーセンタイル", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "99パーセンタイル", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "レート", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "合計", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "オブザーバビリティデータタイプが特定の値以上になったときにアラートを送信します。", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "グループがデータのレポートを停止する場合にアラートで通知する", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "ドキュメント", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "すべての一意の値についてアラートを作成します。例:「host.id」または「cloud.region」。", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "アラートのグループ化条件(オプション)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "カスタム等式", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "集約/フィールドを追加", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "削除", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "基本数学式をサポートします", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "カスタムラベルにはアラートグラフと理由/アラートタイトルに表示されます", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "ラベル(任意)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[この設定は、ドキュメントカウントアグリゲーターには適用されません。]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "集約が必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "等式フィールドでは次の文字のみを使用できます:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "フィルタークエリは無効です。", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "メトリックが必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "しきい値が必要です。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "しきい値には有効な数値を含める必要があります。", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "ページサイズが必要です。", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "以前に検出されたグループが結果を報告しなくなった場合は、これを有効にすると、アクションがトリガーされます。自動的に急速にノードを開始および停止することがある動的に拡張するインフラストラクチャーでは、これは推奨されません。", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "is not between", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "条件を削除", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[データなし]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\}は状態\\{\\{context.alertState\\}\\}です\n\n 理由:\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "アラート", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "データなし", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[データなし]", - "xpack.observability.threshold.rule.alertsButton": "アラートとルール", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたクラウドオブジェクト。", - "xpack.observability.threshold.rule.containerActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたコンテナーオブジェクト。", - "xpack.observability.threshold.rule.createInventoryRuleButton": "インベントリルールの作成", - "xpack.observability.threshold.rule.createThresholdRuleButton": "しきい値ルールを作成", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "データを報告しているグループを含むオブジェクト", - "xpack.observability.threshold.rule.hostActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたホストオブジェクト。", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "インフラストラクチャー", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "インフラストラクチャールール", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "このアラートがトリガーされたエンティティに関連付けられたラベルのリスト。", - "xpack.observability.threshold.rule.manageRules": "ルールの管理", - "xpack.observability.threshold.rule.metricsDropdownMenu": "メトリック", - "xpack.observability.threshold.rule.metricsDropdownTitle": "メトリックルール", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "ソースで使用可能な場合に、ECSで定義されたオーケストレーターオブジェクト。", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "アラートの理由の簡潔な説明", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "ソースの読み込みに失敗しました:HTTPクライアントがありません。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "変更をメトリック構成に適用できませんでした。しばらくたってから再試行してください。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "構成の更新が失敗しました", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "メトリック設定は正常に更新されました", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "このアラートがトリガーされたエンティティに関連付けられたタグのリスト。", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "より大", - "xpack.observability.threshold.rule.threshold.belowRecovery": "より小", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "の間", - "xpack.observability.threshold.rule.threshold.customEquation": "カスタム等式", - "xpack.observability.threshold.rule.threshold.documentCount": "ドキュメントカウント", - "xpack.observability.threshold.rule.timestampDescription": "アラートが検出された時点のタイムスタンプ。", - "xpack.observability.threshold.rule.valueActionVariableDescription": "指定された条件のメトリックの値。使用方法:(ctx.value.condition0、ctx.value.condition1など)。", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "アラートソースにリンク", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "graph/", "xpack.observability.threshold.ruleExplorer.groupByLabel": "すべて", "xpack.observability.threshold.ruleName": "しきい値(テクニカルプレビュー)", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7c0d74cc40459..75fcb531da66a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -27126,18 +27126,6 @@ "xpack.observability.slo.update.errorNotification": "更新 {name} 时出现问题", "xpack.observability.slo.update.successNotification": "成功更新 {name}", "xpack.observability.syntheticsThrottlingEnabledExperimentDescription": "在 Synthetics 监测配置中启用限制设置。请注意,即使该设置处于活动状态,可能仍然无法对您的监测应用限制。仅限内部使用。{link}", - "xpack.observability.threshold.rule.alertDetailsAppSection.criterion.subtitle": "过去 {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.filterLabel": "KQL 筛选 {name}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabel": "过去 {lookback} {timeLabel}", - "xpack.observability.threshold.rule.alerts.dataTimeRangeLabelWithGrouping": "{id} 过去 {lookback} {timeLabel}的数据", - "xpack.observability.threshold.rule.threshold.errorAlertReason": "Elasticsearch 尝试查询 {metric} 的数据时出现故障", - "xpack.observability.threshold.rule.threshold.firedAlertReason": "过去 {duration}{group},{metric} 为 {currentValue}。{comparator} {threshold} 时告警。", - "xpack.observability.threshold.rule.threshold.noDataAlertReason": "对于 {group},{metric} 在过去 {interval}中未报告数据", - "xpack.observability.threshold.rule.threshold.recoveredAlertReason": "对于 {group},{metric} 现在{comparator}阈值 {threshold}(当前值为 {currentValue})", - "xpack.observability.threshold.rule.threshold.thresholdRange": "{a} 和 {b}", - "xpack.observability.threshold.rule.thresholdExtraTitle": "{comparator} {threshold} 时告警", "xpack.observability.transactionRateLabel": "{value} tpm", "xpack.observability.ux.coreVitals.averageMessage": " 且小于 {bad}", "xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd}%)", @@ -27584,81 +27572,6 @@ "xpack.observability.statusVisualization.ux.link": "添加数据", "xpack.observability.statusVisualization.ux.title": "用户体验", "xpack.observability.syntheticsThrottlingEnabledExperimentName": "启用 Synthetics 限制(实验性)", - "xpack.observability.threshold.rule..charts.errorMessage": "哇哦,出问题了", - "xpack.observability.threshold.rule..charts.noDataMessage": "没有可用图表数据", - "xpack.observability.threshold.rule..timeLabels.days": "天", - "xpack.observability.threshold.rule..timeLabels.hours": "小时", - "xpack.observability.threshold.rule..timeLabels.minutes": "分钟", - "xpack.observability.threshold.rule..timeLabels.seconds": "秒", - "xpack.observability.threshold.rule.alertDetailsAppSection.summaryField.rule": "规则", - "xpack.observability.threshold.rule.alertDetailsAppSection.thresholdTitle": "超出阈值", - "xpack.observability.threshold.rule.alertDetailUrlActionVariableDescription": "链接到告警故障排除视图获取进一步的上下文和详情。如果未配置 server.publicBaseUrl,这将为空字符串。", - "xpack.observability.threshold.rule.alertDropdownTitle": "告警和规则", - "xpack.observability.threshold.rule.alertFlyout.addCondition": "添加条件", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.avg": "平均值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.cardinality": "基数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.count": "文档计数", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.max": "最大值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.min": "最小值", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p95": "第 95 个百分位", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.p99": "第 99 个百分位", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.rate": "比率", - "xpack.observability.threshold.rule.alertFlyout.aggregationText.sum": "求和", - "xpack.observability.threshold.rule.alertFlyout.alertDescription": "任何 Observability 数据类型到达或超出给定值时告警。", - "xpack.observability.threshold.rule.alertFlyout.alertOnGroupDisappear": "组停止报告数据时提醒我", - "xpack.observability.threshold.rule.alertFlyout.alertPerRedundantFilterError.docsLink": "文档", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerHelpText": "为每个唯一值创建告警。例如:“host.id”或“cloud.region”。", - "xpack.observability.threshold.rule.alertFlyout.createAlertPerText": "告警分组依据(可选)", - "xpack.observability.threshold.rule.alertFlyout.customEquation": "定制方程", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.addCustomRow": "添加聚合/字段", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.deleteRowButton": "删除", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.equationHelpMessage": "支持基本匹配表达式", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelHelpMessage": "定制标签将在告警图表上以及原因/告警标题中显示", - "xpack.observability.threshold.rule.alertFlyout.customEquationEditor.labelLabel": "标签(可选)", - "xpack.observability.threshold.rule.alertFlyout.docCountNoDataDisabledHelpText": "[此设置不适用于文档计数聚合器。]", - "xpack.observability.threshold.rule.alertFlyout.error.aggregationRequired": "“聚合”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.equation.invalidCharacters": "方程字段仅支持以下字符:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.threshold.rule.alertFlyout.error.invalidFilterQuery": "筛选查询无效。", - "xpack.observability.threshold.rule.alertFlyout.error.metricRequired": "“指标”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdRequired": "“阈值”必填。", - "xpack.observability.threshold.rule.alertFlyout.error.thresholdTypeRequired": "阈值必须包含有效数字。", - "xpack.observability.threshold.rule.alertFlyout.error.timeRequred": "“时间大小”必填。", - "xpack.observability.threshold.rule.alertFlyout.groupDisappearHelpText": "启用此选项可在之前检测的组开始不报告任何数据时触发操作。不建议将此选项用于可能会快速自动启动和停止节点的动态扩展基础架构。", - "xpack.observability.threshold.rule.alertFlyout.outsideRangeLabel": "不介于", - "xpack.observability.threshold.rule.alertFlyout.removeCondition": "删除条件", - "xpack.observability.threshold.rule.alerting.noDataFormattedValue": "[无数据]", - "xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage": "\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\} 处于 \\{\\{context.alertState\\}\\} 状态\n\n 原因:\n \\{\\{context.reason\\}\\}\n ", - "xpack.observability.threshold.rule.alerting.threshold.fired": "告警", - "xpack.observability.threshold.rule.alerting.threshold.nodata": "无数据", - "xpack.observability.threshold.rule.alerting.threshold.noDataFormattedValue": "[无数据]", - "xpack.observability.threshold.rule.alertsButton": "告警和规则", - "xpack.observability.threshold.rule.cloudActionVariableDescription": "ECS 定义的云对象(如果在源中可用)。", - "xpack.observability.threshold.rule.containerActionVariableDescription": "ECS 定义的容器对象(如果在源中可用)。", - "xpack.observability.threshold.rule.createInventoryRuleButton": "创建库存规则", - "xpack.observability.threshold.rule.createThresholdRuleButton": "创建阈值规则", - "xpack.observability.threshold.rule.groupByKeysActionVariableDescription": "包含正报告数据的组的对象", - "xpack.observability.threshold.rule.hostActionVariableDescription": "ECS 定义的主机对象(如果在源中可用)。", - "xpack.observability.threshold.rule.infrastructureDropdownMenu": "基础设施", - "xpack.observability.threshold.rule.infrastructureDropdownTitle": "基础设施规则", - "xpack.observability.threshold.rule.labelsActionVariableDescription": "与在其上触发此告警的实体关联的标签列表。", - "xpack.observability.threshold.rule.manageRules": "管理规则", - "xpack.observability.threshold.rule.metricsDropdownMenu": "指标", - "xpack.observability.threshold.rule.metricsDropdownTitle": "指标规则", - "xpack.observability.threshold.rule.orchestratorActionVariableDescription": "ECS 定义的 Orchestrator 对象(如果在源中可用)。", - "xpack.observability.threshold.rule.reasonActionVariableDescription": "告警原因的简洁描述", - "xpack.observability.threshold.rule.sourceConfiguration.missingHttp": "无法加载源:无 HTTP 客户端可用。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureBody": "无法对指标配置应用更改。请稍后重试。", - "xpack.observability.threshold.rule.sourceConfiguration.updateFailureTitle": "配置更新失败", - "xpack.observability.threshold.rule.sourceConfiguration.updateSuccessTitle": "已成功更新指标设置", - "xpack.observability.threshold.rule.tagsActionVariableDescription": "与在其上触发此告警的实体关联的标记列表。", - "xpack.observability.threshold.rule.threshold.aboveRecovery": "高于", - "xpack.observability.threshold.rule.threshold.belowRecovery": "低于", - "xpack.observability.threshold.rule.threshold.betweenRecovery": "介于", - "xpack.observability.threshold.rule.threshold.customEquation": "定制方程", - "xpack.observability.threshold.rule.threshold.documentCount": "文档计数", - "xpack.observability.threshold.rule.timestampDescription": "检测到告警时的时间戳。", - "xpack.observability.threshold.rule.valueActionVariableDescription": "指定条件中的指标值。用法:(ctx.value.condition0, ctx.value.condition1, 诸如此类)。", - "xpack.observability.threshold.rule.viewInAppUrlActionVariableDescription": "链接到告警源", "xpack.observability.threshold.ruleExplorer.groupByAriaLabel": "图表绘制依据", "xpack.observability.threshold.ruleExplorer.groupByLabel": "所有内容", "xpack.observability.threshold.ruleName": "阈值(技术预览)", diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts similarity index 90% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts index 7bb8874dafed9..8df0f20e88de7 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts @@ -6,8 +6,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; @@ -22,8 +25,8 @@ export default function ({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); - describe('Threshold rule - AVG - PCT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - AVG - PCT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -44,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -131,13 +134,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -145,14 +148,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts similarity index 90% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts index c0138d993e6bd..d57398def6381 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -5,8 +5,11 @@ * 2.0. */ -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; @@ -20,8 +23,8 @@ export default function ({ getService }: FtrProviderContext) { const esClient = getService('es'); const supertest = getService('supertest'); - describe('Threshold rule - AVG - PCT - NoData', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - AVG - PCT - NoData', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id-no-data'; let actionId: string; @@ -40,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -125,13 +128,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -139,14 +142,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.nodata' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts similarity index 92% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts index 1c35793e04337..b4570771b9f2f 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/avg_us_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts @@ -8,8 +8,11 @@ import moment from 'moment'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { format } from 'url'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -33,8 +36,8 @@ export default function ({ getService }: FtrProviderContext) { const kibanaUrl = format(kibanaServerConfig); const supertest = getService('supertest'); - describe('Threshold rule - AVG - US - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - AVG - US - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; @@ -60,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esDeleteAllIndices([ALERT_ACTION_INDEX]); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -149,7 +152,7 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -157,7 +160,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -165,14 +168,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); @@ -210,7 +216,7 @@ export default function ({ getService }: FtrProviderContext) { indexName: ALERT_ACTION_INDEX, }); - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts similarity index 91% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 794902f19688f..8499511391ab6 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -12,8 +12,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; @@ -28,8 +31,8 @@ export default function ({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); - describe('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -50,7 +53,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -139,13 +142,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -153,14 +156,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts similarity index 90% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts index 3361f4a04e185..99f313960fa6b 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/documents_count_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -6,8 +6,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; @@ -22,8 +25,8 @@ export default function ({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); - describe('Threshold rule - DOCUMENTS_COUNT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -44,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -129,13 +132,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -143,14 +146,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts similarity index 92% rename from x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts index 9d3e5e2bd17ad..5925907b471b6 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule/group_by_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -13,8 +13,11 @@ import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; @@ -35,8 +38,8 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; let startedAt: string; - describe('Threshold rule - GROUP_BY - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe('Custom Threshold rule - GROUP_BY - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -57,7 +60,7 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -149,7 +152,7 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await waitForAlertInIndex({ esClient, - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -157,7 +160,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -165,14 +168,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); @@ -220,7 +226,7 @@ export default function ({ getService }: FtrProviderContext) { indexName: ALERT_ACTION_INDEX, }); - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); diff --git a/x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts similarity index 96% rename from x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts rename to x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts index f4d26f961c234..3119a0ae46e63 100644 --- a/x-pack/test/alerting_api_integration/observability/threshold_rule_data_view.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../common/ftr_provider_context'; @@ -20,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { const objectRemover = new ObjectRemover(supertest); const es = getService('es'); - describe('Threshold rule data view >', () => { + describe('Custom Threshold rule data view >', () => { const DATA_VIEW_ID = 'data-view-id'; let ruleId: string; diff --git a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts index a50e1b4e85c14..4bde235e97dd2 100644 --- a/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts +++ b/x-pack/test/alerting_api_integration/observability/helpers/alerting_api_helper.ts @@ -6,7 +6,7 @@ */ import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; -import { ThresholdParams } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import type { SuperTest, Test } from 'supertest'; export async function createIndexConnector({ diff --git a/x-pack/test/alerting_api_integration/observability/index.ts b/x-pack/test/alerting_api_integration/observability/index.ts index 7bc5a5caab0b2..884c17d2abfd1 100644 --- a/x-pack/test/alerting_api_integration/observability/index.ts +++ b/x-pack/test/alerting_api_integration/observability/index.ts @@ -10,13 +10,13 @@ export default function ({ loadTestFile }: any) { describe('Observability Rules', () => { describe('Rules Endpoints', () => { loadTestFile(require.resolve('./metric_threshold_rule')); - loadTestFile(require.resolve('./threshold_rule/avg_pct_fired')); - loadTestFile(require.resolve('./threshold_rule/avg_pct_no_data')); - loadTestFile(require.resolve('./threshold_rule/avg_us_fired')); - loadTestFile(require.resolve('./threshold_rule/custom_eq_avg_bytes_fired')); - loadTestFile(require.resolve('./threshold_rule/documents_count_fired')); - loadTestFile(require.resolve('./threshold_rule/group_by_fired')); - loadTestFile(require.resolve('./threshold_rule_data_view')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_no_data')); + loadTestFile(require.resolve('./custom_threshold_rule/avg_us_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/custom_eq_avg_bytes_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/documents_count_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/group_by_fired')); + loadTestFile(require.resolve('./custom_threshold_rule_data_view')); }); describe('Synthetics', () => { loadTestFile(require.resolve('./synthetics_rule')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts index 40197f1e18783..e748478b18432 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts @@ -63,7 +63,7 @@ export default function createRegisteredRuleTypeTests({ getService }: FtrProvide 'monitoring_alert_elasticsearch_version_mismatch', 'monitoring_ccr_read_exceptions', 'monitoring_shard_size', - 'observability.rules.threshold', + 'observability.rules.custom_threshold', 'apm.transaction_duration', 'apm.anomaly', 'apm.error_rate', diff --git a/x-pack/test_serverless/api_integration/services/alerting_api.ts b/x-pack/test_serverless/api_integration/services/alerting_api.ts index 8eb771d7eb11d..668dcdab82aa0 100644 --- a/x-pack/test_serverless/api_integration/services/alerting_api.ts +++ b/x-pack/test_serverless/api_integration/services/alerting_api.ts @@ -11,7 +11,7 @@ import type { } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; -import { ThresholdParams } from '@kbn/observability-plugin/common/threshold_rule/types'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FtrProviderContext } from '../ftr_provider_context'; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts similarity index 90% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts index 77114463eb89f..f75faa2b2f686 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -6,8 +6,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -22,8 +25,8 @@ export default function ({ getService }: FtrProviderContext) { // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - AVG - PCT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe.skip('Custom Threshold rule - AVG - PCT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -49,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -131,13 +134,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -145,14 +148,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts similarity index 90% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts index cbfdf251ba602..fbcb7a1404293 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -5,8 +5,11 @@ * 2.0. */ -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -19,8 +22,8 @@ export default function ({ getService }: FtrProviderContext) { // Blocked API: index_not_found_exception: no such index [.alerts-observability.threshold.alerts-default] // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - AVG - PCT - NoData', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe.skip('Custom Threshold rule - AVG - PCT - NoData', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id-no-data'; let actionId: string; @@ -44,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -124,13 +127,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -138,14 +141,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.nodata' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts similarity index 91% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 81b57e492f100..3e996e555b092 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -12,8 +12,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -27,8 +30,8 @@ export default function ({ getService }: FtrProviderContext) { const dataViewApi = getService('dataViewApi'); // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe.skip('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -54,7 +57,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -138,13 +141,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -152,14 +155,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts similarity index 90% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts index ad0624a91dfb1..1fa95b8cab8a9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/documents_count_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -6,8 +6,11 @@ */ import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -21,8 +24,8 @@ export default function ({ getService }: FtrProviderContext) { const dataViewApi = getService('dataViewApi'); // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - DOCUMENTS_COUNT - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe.skip('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -48,7 +51,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -128,13 +131,13 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -142,14 +145,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts similarity index 92% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index 5cadfbc6b3d9d..8bd8312a5184a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -14,8 +14,11 @@ import { kbnTestConfig } from '@kbn/test'; import moment from 'moment'; import { cleanup, generate } from '@kbn/infra-forge'; -import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -31,8 +34,8 @@ export default function ({ getService }: FtrProviderContext) { let startedAt: string; // Issue: https://github.com/elastic/kibana/issues/165138 - describe.skip('Threshold rule - GROUP_BY - FIRED', () => { - const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + describe.skip('Custom Threshold rule - GROUP_BY - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_ID = 'data-view-id'; let infraDataIndex: string; @@ -58,7 +61,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); await esClient.deleteByQuery({ - index: THRESHOLD_RULE_ALERT_INDEX, + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ @@ -145,7 +148,7 @@ export default function ({ getService }: FtrProviderContext) { it('should set correct information in the alert document', async () => { const resp = await alertingApi.waitForAlertInIndex({ - indexName: THRESHOLD_RULE_ALERT_INDEX, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -153,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.category', - 'Threshold (Technical Preview)' + 'Custom threshold (BETA)' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); @@ -161,14 +164,17 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); expect(resp.hits.hits[0]._source).property( 'kibana.alert.rule.rule_type_id', - 'observability.rules.threshold' + 'observability.rules.custom_threshold' ); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); expect(resp.hits.hits[0]._source) .property('kibana.alert.rule.tags') .contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); expect(resp.hits.hits[0]._source).property('tags').contain('observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0'); expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); @@ -216,7 +222,7 @@ export default function ({ getService }: FtrProviderContext) { }); const { protocol, hostname, port } = kbnTestConfig.getUrlParts(); - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold'); + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( `${protocol}://${hostname}:${port}/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts similarity index 93% rename from x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts rename to x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts index dbb8968d2d946..944068af6a21b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/threshold_rule/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('Threshold Rule', function () { + describe('Custom Threshold Rule', function () { loadTestFile(require.resolve('./avg_pct_fired')); loadTestFile(require.resolve('./avg_pct_no_data')); loadTestFile(require.resolve('./documents_count_fired')); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts index d9643f91d70ae..a3a5ab552ee3f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts @@ -9,6 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Serverless observability API - feature flags', function () { - loadTestFile(require.resolve('./threshold_rule')); + loadTestFile(require.resolve('./custom_threshold_rule')); }); } From 6561cd2e0c5eb783ec1c2f18fd9041834927334b Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Mon, 18 Sep 2023 22:52:19 +1000 Subject: [PATCH 11/58] Update kubernetes templates for elastic-agent (#166594) Automated by https://fleet-ci.elastic.co/job/elastic-agent/job/elastic-agent-mbp/job/main/1360/ --------- Co-authored-by: obscloudnativemonitoring Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/server/services/elastic_agent_manifest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts index 578625d1a38ab..385b1095cb9f4 100644 --- a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts +++ b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts @@ -42,7 +42,7 @@ spec: # - -c # - >- # mkdir -p /etc/elastic-agent/inputs.d && - # wget -O - https://github.com/elastic/elastic-agent/archive/main.tar.gz | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-main/deploy/kubernetes/elastic-agent-standalone/templates.d" + # wget -O - https://github.com/elastic/elastic-agent/archive/main.tar.gz | tar xz -C /etc/elastic-agent/inputs.d --strip=5 "elastic-agent-main/deploy/kubernetes/elastic-agent/templates.d" # volumeMounts: # - name: external-inputs # mountPath: /etc/elastic-agent/inputs.d @@ -71,7 +71,7 @@ spec: # The following ELASTIC_NETINFO:false variable will disable the netinfo.enabled option of add-host-metadata processor. This will remove fields host.ip and host.mac. # For more info: https://www.elastic.co/guide/en/beats/metricbeat/current/add-host-metadata.html - name: ELASTIC_NETINFO - value: "false" + value: "false" securityContext: runAsUser: 0 # The following capabilities are needed for 'Defend for containers' integration (cloud-defend) @@ -396,7 +396,7 @@ spec: # The following ELASTIC_NETINFO:false variable will disable the netinfo.enabled option of add-host-metadata processor. This will remove fields host.ip and host.mac. # For more info: https://www.elastic.co/guide/en/beats/metricbeat/current/add-host-metadata.html - name: ELASTIC_NETINFO - value: "false" + value: "false" securityContext: runAsUser: 0 # The following capabilities are needed for 'Defend for containers' integration (cloud-defend) From 555dd7e75e469d79c6d36c22673dc19495d0b7b7 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 18 Sep 2023 14:55:56 +0200 Subject: [PATCH 12/58] FTR - Adjust check for successful navigation (#166605) ## Summary This PR adjusts the FTR check for successful navigation to also take ports on the `appUrl` into account when comparing to `currentUrl` that already had the ports removed for the comparison. --- test/functional/page_objects/common_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 1424d952653cd..4218d19712aa0 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -299,7 +299,7 @@ export class CommonPageObject extends FtrService { const navSuccessful = currentUrl .replace(':80/', '/') .replace(':443/', '/') - .startsWith(appUrl); + .startsWith(appUrl.replace(':80/', '/').replace(':443/', '/')); if (!navSuccessful) { const msg = `App failed to load: ${appName} in ${this.defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; From faf87740988d9544bcb380dbc57323ccd52416f4 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:08:44 +0200 Subject: [PATCH 13/58] New tags do not show spurious suggestions (#166229) ## Summary Closes https://github.com/elastic/kibana/issues/127509 --- .../public/components/edition_modal/create_or_edit_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_or_edit_modal.tsx b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_or_edit_modal.tsx index ac4a2ac490b4d..f972220843b47 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_or_edit_modal.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_or_edit_modal.tsx @@ -125,7 +125,7 @@ export const CreateOrEditModal: FC = ({ error={validation.errors.name} > Date: Mon, 18 Sep 2023 15:39:52 +0200 Subject: [PATCH 14/58] [kbn-es] use the same method to wait for cluster status (#166332) ## Summary Removing `cluster.waitForClusterReady` in favour of unified `waitUntilClusterReady` method. We will keep waiting for `yellow` cluster status in the FTR stateful tests and `green` in serverless tests. --- packages/kbn-es/src/cluster.ts | 49 ++--------- .../src/integration_tests/cluster.test.ts | 69 ++++++++++----- packages/kbn-es/src/utils/docker.test.ts | 37 ++++---- packages/kbn-es/src/utils/docker.ts | 5 +- .../utils/wait_until_cluster_ready.test.ts | 86 +++++++++++++++++++ .../src/utils/wait_until_cluster_ready.ts | 22 ++++- 6 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts diff --git a/packages/kbn-es/src/cluster.ts b/packages/kbn-es/src/cluster.ts index 76c1119ab3584..cf61d768d7b0c 100644 --- a/packages/kbn-es/src/cluster.ts +++ b/packages/kbn-es/src/cluster.ts @@ -42,8 +42,7 @@ import { InstallSnapshotOptions, InstallSourceOptions, } from './install/types'; - -const DEFAULT_READY_TIMEOUT = 60 * 1000; +import { waitUntilClusterReady } from './utils/wait_until_cluster_ready'; // listen to data on stream until map returns anything but undefined const firstResult = (stream: Readable, map: (data: Buffer) => string | true | undefined) => @@ -426,7 +425,12 @@ export class Cluster { }); if (!skipReadyCheck) { - await this.waitForClusterReady(client, readyTimeout); + await waitUntilClusterReady({ + client, + expectedStatus: 'yellow', + log: this.log, + readyTimeout, + }); } // once the cluster is ready setup the native realm @@ -509,45 +513,6 @@ export class Cluster { }); } - async waitForClusterReady(client: Client, readyTimeout = DEFAULT_READY_TIMEOUT) { - let attempt = 0; - const start = Date.now(); - - this.log.info('waiting for ES cluster to report a yellow or green status'); - - while (true) { - attempt += 1; - - try { - const resp = await client.cluster.health(); - if (resp.status !== 'red') { - return; - } - - throw new Error(`not ready, cluster health is ${resp.status}`); - } catch (error) { - const timeSinceStart = Date.now() - start; - if (timeSinceStart > readyTimeout) { - const sec = readyTimeout / 1000; - throw new Error(`ES cluster failed to come online with the ${sec} second timeout`); - } - - if (error.message.startsWith('not ready,')) { - if (timeSinceStart > 10_000) { - this.log.warning(error.message); - } - } else { - this.log.warning( - `waiting for ES cluster to come online, attempt ${attempt} failed with: ${error.message}` - ); - } - - const waitSec = attempt * 1.5; - await new Promise((resolve) => setTimeout(resolve, waitSec * 1000)); - } - } - } - private getJavaOptions(opts: string | undefined) { let esJavaOpts = `${opts || ''} ${process.env.ES_JAVA_OPTS || ''}`; // ES now automatically sets heap size to 50% of the machine's available memory diff --git a/packages/kbn-es/src/integration_tests/cluster.test.ts b/packages/kbn-es/src/integration_tests/cluster.test.ts index dfe3d25f5ec65..cf6bba5df669b 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.ts +++ b/packages/kbn-es/src/integration_tests/cluster.test.ts @@ -12,6 +12,7 @@ import * as extractConfig from '../utils/extract_config_files'; import * as dockerUtils from '../utils/docker'; import { createAnyInstanceSerializer, createStripAnsiSerializer } from '@kbn/jest-serializers'; import * as installUtils from '../install'; +import * as waitClusterUtil from '../utils/wait_until_cluster_ready'; import { Cluster } from '../cluster'; import { ES_NOPASSWORD_P12_PATH } from '@kbn/dev-utils/src/certs'; import { @@ -20,8 +21,10 @@ import { InstallSnapshotOptions, InstallSourceOptions, } from '../install/types'; +import { Client } from '@elastic/elasticsearch'; expect.addSnapshotSerializer(createAnyInstanceSerializer(ToolingLog)); +expect.addSnapshotSerializer(createAnyInstanceSerializer(Client)); expect.addSnapshotSerializer(createStripAnsiSerializer()); const log = new ToolingLog(); @@ -101,6 +104,10 @@ jest.mock('../utils/docker', () => ({ runDockerContainer: jest.fn(), })); +jest.mock('../utils/wait_until_cluster_ready', () => ({ + waitUntilClusterReady: jest.fn(), +})); + const downloadSnapshotMock = jest.spyOn(installUtils, 'downloadSnapshot'); const installSourceMock = jest.spyOn(installUtils, 'installSource'); const installSnapshotMock = jest.spyOn(installUtils, 'installSnapshot'); @@ -108,6 +115,7 @@ const installArchiveMock = jest.spyOn(installUtils, 'installArchive'); const extractConfigFilesMock = jest.spyOn(extractConfig, 'extractConfigFiles'); const runServerlessClusterMock = jest.spyOn(dockerUtils, 'runServerlessCluster'); const runDockerContainerMock = jest.spyOn(dockerUtils, 'runDockerContainer'); +const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady'); beforeEach(() => { jest.resetAllMocks(); @@ -366,6 +374,40 @@ describe('#start(installPath)', () => { expect(fs.existsSync(writeLogsToPath)).toBe(true); }); + test('calls waitUntilClusterReady() by default', async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockResolvedValue(); + + await new Cluster({ log }).start(installPath, esClusterExecOptions); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(1); + expect(waitUntilClusterReadyMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "client": , + "expectedStatus": "yellow", + "log": , + "readyTimeout": undefined, + }, + ] + `); + }); + + test(`doesn't call waitUntilClusterReady() if 'skipReadyCheck' is passed`, async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockResolvedValue(); + + await new Cluster({ log }).start(installPath, { skipReadyCheck: true }); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(0); + }); + + test(`rejects if waitUntilClusterReady() rejects`, async () => { + mockEsBin({ start: true }); + waitUntilClusterReadyMock.mockRejectedValue(new Error('foo')); + await expect( + new Cluster({ log }).start(installPath, esClusterExecOptions) + ).rejects.toThrowError('foo'); + }); + test('rejects if #start() was called previously', async () => { mockEsBin({ start: true }); @@ -750,18 +792,8 @@ describe('#runServerless()', () => { waitForReady: true, }; await cluster.runServerless(serverlessOptions); - expect(runServerlessClusterMock.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - , - Object { - "background": true, - "basePath": "${installPath}", - "clean": true, - "teardown": true, - "waitForReady": true, - }, - ] - `); + expect(runServerlessClusterMock.mock.calls[0][0]).toMatchInlineSnapshot(``); + expect(runServerlessClusterMock.mock.calls[0][1]).toBe(serverlessOptions); }); }); @@ -810,17 +842,12 @@ describe('#runDocker()', () => { }); test('passes through all options+log to #runDockerContainer()', async () => { + const options = { dockerCmd: 'start -a es01' }; runDockerContainerMock.mockResolvedValueOnce(); const cluster = new Cluster({ log }); - await cluster.runDocker({ dockerCmd: 'start -a es01' }); - expect(runDockerContainerMock.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - , - Object { - "dockerCmd": "start -a es01", - }, - ] - `); + await cluster.runDocker(options); + expect(runDockerContainerMock.mock.calls[0][0]).toMatchInlineSnapshot(``); + expect(runDockerContainerMock.mock.calls[0][1]).toBe(options); }); }); diff --git a/packages/kbn-es/src/utils/docker.test.ts b/packages/kbn-es/src/utils/docker.test.ts index 4856aa6d1b17d..1d4f1a71468af 100644 --- a/packages/kbn-es/src/utils/docker.test.ts +++ b/packages/kbn-es/src/utils/docker.test.ts @@ -33,6 +33,7 @@ import { import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; import { ES_P12_PATH } from '@kbn/dev-utils'; import { ESS_CONFIG_PATH, ESS_RESOURCES_PATHS, ESS_SECRETS_PATH, ESS_JWKS_PATH } from '../paths'; +import * as waitClusterUtil from './wait_until_cluster_ready'; jest.mock('execa'); const execa = jest.requireMock('execa'); @@ -42,6 +43,10 @@ jest.mock('@elastic/elasticsearch', () => { }; }); +jest.mock('./wait_until_cluster_ready', () => ({ + waitUntilClusterReady: jest.fn(), +})); + const log = new ToolingLog(); const logWriter = new ToolingLogCollectingWriter(); log.setWriters([logWriter]); @@ -51,6 +56,8 @@ const baseEsPath = `${KIBANA_ROOT}/.es`; const serverlessDir = 'stateless'; const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`; +const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady'); + beforeEach(() => { jest.resetAllMocks(); log.indent(-log.getIndent()); @@ -473,24 +480,18 @@ describe('runServerlessCluster()', () => { // setupDocker execa calls then run three nodes and attach logger expect(execa.mock.calls).toHaveLength(8); }); - describe('waitForReady', () => { - test('should wait for serverless nodes to be ready to serve requests', async () => { - mockFs({ - [baseEsPath]: {}, - }); - execa.mockImplementation(() => Promise.resolve({ stdout: '' })); - const health = jest.fn(); - jest - .requireMock('@elastic/elasticsearch') - .Client.mockImplementation(() => ({ cluster: { health } })); - - health.mockImplementationOnce(() => Promise.reject()); // first call fails - health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); // second call return wrong status - health.mockImplementationOnce(() => Promise.resolve({ status: 'green' })); // then succeeds - - await runServerlessCluster(log, { basePath: baseEsPath, waitForReady: true }); - expect(health).toHaveBeenCalledTimes(3); - }, 10000); + + test(`should wait for serverless nodes to return 'green' status`, async () => { + waitUntilClusterReadyMock.mockResolvedValue(); + mockFs({ + [baseEsPath]: {}, + }); + execa.mockImplementation(() => Promise.resolve({ stdout: '' })); + + await runServerlessCluster(log, { basePath: baseEsPath, waitForReady: true }); + expect(waitUntilClusterReadyMock).toHaveBeenCalledTimes(1); + expect(waitUntilClusterReadyMock.mock.calls[0][0].expectedStatus).toEqual('green'); + expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined); }); }); diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts index 00adea7e07cd6..1369af0e1adab 100644 --- a/packages/kbn-es/src/utils/docker.ts +++ b/packages/kbn-es/src/utils/docker.ts @@ -349,7 +349,7 @@ export async function maybePullDockerImage(log: ToolingLog, image: string) { stdio: ['ignore', 'inherit', 'pipe'], }).catch(({ message }) => { throw createCliError( - `Error pulling image. This is likely an issue authenticating with ${DOCKER_REGISTRY}. + `Error pulling image. This is likely an issue authenticating with ${DOCKER_REGISTRY}. Visit ${chalk.bold.cyan('https://docker-auth.elastic.co/github_auth')} to login. ${message}` @@ -622,8 +622,7 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO } : {}), }); - await waitUntilClusterReady({ client, log }); - log.success('ES is ready'); + await waitUntilClusterReady({ client, expectedStatus: 'green', log }); } if (options.teardown) { diff --git a/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts b/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts new file mode 100644 index 0000000000000..c662ba4ecaf8c --- /dev/null +++ b/packages/kbn-es/src/utils/wait_until_cluster_ready.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; +import { waitUntilClusterReady } from './wait_until_cluster_ready'; + +jest.mock('@elastic/elasticsearch', () => { + return { + Client: jest.fn(), + }; +}); + +const log = new ToolingLog(); +const logWriter = new ToolingLogCollectingWriter(); +log.setWriters([logWriter]); + +const health = jest.fn(); + +beforeEach(() => { + jest.resetAllMocks(); + jest + .requireMock('@elastic/elasticsearch') + .Client.mockImplementation(() => ({ cluster: { health } })); + log.indent(-log.getIndent()); + logWriter.messages.length = 0; +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('waitUntilClusterReady', () => { + test(`waits for node to return 'green' status`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'yellow' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'green' })); // 4th call returns expected status + + const client = new Client({}); + + await waitUntilClusterReady({ client, log, expectedStatus: 'green' }); + expect(health).toHaveBeenCalledTimes(4); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info waiting for ES cluster to report a green status", + " warn waiting for ES cluster to come online, attempt 1 failed with: foo", + " succ ES cluster is ready", + ] + `); + }, 10000); + + test(`waits for node to return 'yellow' status`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'YELLOW' })); // 3rd call returns expected status + health.mockImplementationOnce(() => Promise.resolve({ status: 'yellow' })); + health.mockImplementationOnce(() => Promise.resolve({ status: 'green' })); + + const client = new Client({}); + + await waitUntilClusterReady({ client, log, expectedStatus: 'yellow' }); + expect(health).toHaveBeenCalledTimes(3); + expect(logWriter.messages).toMatchInlineSnapshot(` + Array [ + " info waiting for ES cluster to report a yellow status", + " warn waiting for ES cluster to come online, attempt 1 failed with: foo", + " succ ES cluster is ready", + ] + `); + }, 10000); + + test(`rejects when 'readyTimeout' is exceeded`, async () => { + health.mockImplementationOnce(() => Promise.reject(new Error('foo'))); + health.mockImplementationOnce(() => Promise.resolve({ status: 'red' })); + const client = new Client({}); + await expect( + waitUntilClusterReady({ client, log, expectedStatus: 'yellow', readyTimeout: 1000 }) + ).rejects.toThrow('ES cluster failed to come online with the 1 second timeout'); + }); +}); diff --git a/packages/kbn-es/src/utils/wait_until_cluster_ready.ts b/packages/kbn-es/src/utils/wait_until_cluster_ready.ts index b8611253f89d3..99fa4a25c2071 100644 --- a/packages/kbn-es/src/utils/wait_until_cluster_ready.ts +++ b/packages/kbn-es/src/utils/wait_until_cluster_ready.ts @@ -7,38 +7,52 @@ */ import { Client } from '@elastic/elasticsearch'; +import { HealthStatus } from '@elastic/elasticsearch/lib/api/types'; import { ToolingLog } from '@kbn/tooling-log'; const DEFAULT_READY_TIMEOUT = 60 * 1000; // 1 minute +export type ClusterReadyStatus = 'green' | 'yellow'; export interface WaitOptions { client: Client; + expectedStatus: ClusterReadyStatus; log: ToolingLog; readyTimeout?: number; } +const checkStatus = (readyStatus: ClusterReadyStatus) => { + return readyStatus === 'yellow' + ? (status: HealthStatus) => status.toLocaleLowerCase() !== 'red' + : (status: HealthStatus) => status.toLocaleLowerCase() === 'green'; +}; + /** - * General method to wait for the ES cluster status to be green + * General method to wait for the ES cluster status to be yellow or green */ export async function waitUntilClusterReady({ client, + expectedStatus, log, readyTimeout = DEFAULT_READY_TIMEOUT, }: WaitOptions) { let attempt = 0; const start = Date.now(); - log.info('waiting for ES cluster to report a green status'); + log.info(`waiting for ES cluster to report a ${expectedStatus} status`); + + const isReady = checkStatus(expectedStatus); while (true) { attempt += 1; try { const resp = await client.cluster.health(); - if (resp.status === 'green') { + const status: HealthStatus = resp.status; + if (isReady(status)) { + log.success('ES cluster is ready'); return; } - throw new Error(`not ready, cluster health is ${resp.status}`); + throw new Error(`not ready, cluster health is ${status}`); } catch (error) { const timeSinceStart = Date.now() - start; if (timeSinceStart > readyTimeout) { From 0215ed3a0f54823b816fc24d5efa931cd1d55b10 Mon Sep 17 00:00:00 2001 From: natasha-moore-elastic <137783811+natasha-moore-elastic@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:55:57 +0100 Subject: [PATCH 15/58] [DOCS] Adds shards object to Create pack and Update pack API (#166363) ## Summary - Resolves https://github.com/elastic/security-docs/issues/3822 Adds the `shards` object schema definition to Create pack and Update pack API, and to the Create pack request example. - Related dev PR: https://github.com/elastic/kibana/pull/166178 --- docs/api/osquery-manager/packs/create.asciidoc | 14 ++++++++++++-- docs/api/osquery-manager/packs/update.asciidoc | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/api/osquery-manager/packs/create.asciidoc b/docs/api/osquery-manager/packs/create.asciidoc index 5f9829164b245..2fcfc58d43dba 100644 --- a/docs/api/osquery-manager/packs/create.asciidoc +++ b/docs/api/osquery-manager/packs/create.asciidoc @@ -33,6 +33,8 @@ experimental[] Create packs. `policy_ids`:: (Optional, array) A list of agents policy IDs. +`shards`:: (Required, object) An object with shard configuration for policies included in the pack. For each policy, set the shard configuration to a percentage (1–100) of target hosts. + `queries`:: (Required, object) An object of queries. @@ -56,8 +58,13 @@ $ curl -X POST api/osquery/packs \ "description": "My pack", "enabled": true, "policy_ids": [ - "my_policy_id" + "my_policy_id", + "fleet-server-policy" ], + "shards": { + "my_policy_id": 35, + "fleet-server-policy": 58 + }, "queries": { "my_query": { "query": "SELECT * FROM listening_ports;", @@ -67,7 +74,10 @@ $ curl -X POST api/osquery/packs \ "field": "port" }, "tags": { - "value": ["tag1", "tag2"] + "value": [ + "tag1", + "tag2" + ] } } } diff --git a/docs/api/osquery-manager/packs/update.asciidoc b/docs/api/osquery-manager/packs/update.asciidoc index 0fc2e2684e0e9..d098d2567f1ac 100644 --- a/docs/api/osquery-manager/packs/update.asciidoc +++ b/docs/api/osquery-manager/packs/update.asciidoc @@ -38,6 +38,8 @@ WARNING: You are unable to update a prebuilt pack (`read_only = true`). `policy_ids`:: (Optional, array) A list of agent policy IDs. +`shards`:: (Optional, object) An object with shard configuration for policies included in the pack. For each policy, set the shard configuration to a percentage (1–100) of target hosts. + `queries`:: (Required, object) An object of queries. From be38c9d2e4e5e3e2a8c36eee3752a04a15cd47b0 Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Mon, 18 Sep 2023 10:08:26 -0400 Subject: [PATCH 16/58] [DOCS] Add 8.10.1 release notes (#166549) --- docs/CHANGELOG.asciidoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 765f1949ff5f5..045cdb5e80bd7 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -48,6 +49,26 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.10.1]] +== {kib} 8.10.1 + +The 8.10.1 release includes the following bug fixes. + +[float] +[[fixes-v8.10.1]] +=== Bug Fixes + +Dashboard:: +* Fixes content editor flyout footer ({kibana-pull}165907[#165907]). +Elastic Security:: +For the Elastic Security 8.10.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* Show snapshot version in agent upgrade modal and allow custom values ({kibana-pull}165978[#165978]). +Observability:: +* Fix(slo): Use comma-separarted list of source index for transform ({kibana-pull}166294[#166294]). +Presentation:: +* Fixes air-gapped enviroment hitting `400` error when loading fonts for layer ({kibana-pull}165986[#165986]). + [[release-notes-8.10.0]] == {kib} 8.10.0 From 8220c9bc4c30698b0044850091e8bba13c386d82 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Mon, 18 Sep 2023 10:17:17 -0400 Subject: [PATCH 17/58] [Security Solution] [Detections] Adds support for index patterns (DataViewBase) to be used for query bar filters (#166318) ## Summary Ref: https://github.com/elastic/kibana/issues/164265 --- .../components/query_bar/index.test.tsx | 202 ++++++------------ .../common/components/query_bar/index.tsx | 32 ++- 2 files changed, 89 insertions(+), 145 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx index 118c78e290759..934e923d5724d 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.test.tsx @@ -16,9 +16,45 @@ import { SearchBar } from '@kbn/unified-search-plugin/public'; import type { QueryBarComponentProps } from '.'; import { QueryBar } from '.'; +import type { DataViewFieldMap } from '@kbn/data-views-plugin/common'; +import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { fields } from '@kbn/data-views-plugin/common/mocks'; +import { useKibana } from '../../lib/kibana'; + +const getMockIndexPattern = () => ({ + ...createStubDataView({ + spec: { + id: '1234', + title: 'logstash-*', + fields: ((): DataViewFieldMap => { + const fieldMap: DataViewFieldMap = Object.create(null); + for (const field of fields) { + fieldMap[field.name] = { ...field }; + } + return fieldMap; + })(), + }, + }), +}); + const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; +jest.mock('../../lib/kibana'); describe('QueryBar ', () => { + const mockClearInstanceCache = jest.fn().mockImplementation(({ id }: { id: string }) => { + return id; + }); + + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn().mockResolvedValue(getMockIndexPattern()), + clearInstanceCache: mockClearInstanceCache, + }, + }, + }, + }); const mockOnChangeQuery = jest.fn(); const mockOnSubmitQuery = jest.fn(); const mockOnSavedQuery = jest.fn(); @@ -52,10 +88,10 @@ describe('QueryBar ', () => { mockOnSavedQuery.mockClear(); }); - test('check if we format the appropriate props to QueryBar', () => { - const wrapper = mount( - - { + await act(async () => { + const wrapper = await getWrapper( + { onSubmitQuery={mockOnSubmitQuery} onSavedQuery={mockOnSavedQuery} /> - - ); - const { - customSubmitButton, - timeHistory, - onClearSavedQuery, - onFiltersUpdated, - onQueryChange, - onQuerySubmit, - onSaved, - onSavedQueryUpdated, - ...searchBarProps - } = wrapper.find(SearchBar).props(); + ); - expect(searchBarProps).toEqual({ - dataTestSubj: undefined, - dateRangeFrom: 'now/d', - dateRangeTo: 'now/d', - displayStyle: undefined, - filters: [], - indexPatterns: [ - { - fields: [ - { - aggregatable: true, - name: '@timestamp', - searchable: true, - type: 'date', - }, - { - aggregatable: true, - name: '@version', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.ephemeral_id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.hostname', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.id', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test1', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test2', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test3', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test4', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test5', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test6', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test7', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'agent.test8', - searchable: true, - type: 'string', - }, - { - aggregatable: true, - name: 'host.name', - searchable: true, - type: 'string', - }, - { - aggregatable: false, - name: 'nestedField.firstAttributes', - searchable: true, - type: 'string', - }, - { - aggregatable: false, - name: 'nestedField.secondAttributes', - searchable: true, - type: 'string', - }, - ], - title: 'filebeat-*,auditbeat-*,packetbeat-*', - }, - ], - isLoading: false, - isRefreshPaused: true, - query: { - language: 'kuery', - query: 'here: query', - }, - refreshInterval: undefined, - savedQuery: undefined, - showAutoRefreshOnly: false, - showDatePicker: false, - showFilterBar: true, - showQueryInput: true, - showSaveQuery: true, - showSubmitButton: false, + await waitFor(() => { + wrapper.update(); + const { + customSubmitButton, + timeHistory, + onClearSavedQuery, + onFiltersUpdated, + onQueryChange, + onQuerySubmit, + onSaved, + onSavedQueryUpdated, + ...searchBarProps + } = wrapper.find(SearchBar).props(); + expect((searchBarProps?.indexPatterns ?? [{ id: 'unknown' }])[0].id).toEqual( + getMockIndexPattern().id + ); + }); + // ensure useEffect cleanup is called correctly after component unmounts + wrapper.unmount(); + expect(mockClearInstanceCache).toHaveBeenCalledWith(getMockIndexPattern().id); }); }); @@ -294,7 +215,6 @@ describe('QueryBar ', () => { const onSubmitQueryRef = searchBarProps.onQuerySubmit; const onSavedQueryRef = searchBarProps.onSavedQueryUpdated; wrapper.setProps({ onSavedQuery: jest.fn() }); - wrapper.update(); expect(onSavedQueryRef).not.toEqual(wrapper.find(SearchBar).props().onSavedQueryUpdated); expect(onChangedQueryRef).toEqual(wrapper.find(SearchBar).props().onQueryChange); diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index d86f3de10b549..9356956c23d56 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; +import React, { memo, useMemo, useCallback, useState, useEffect } from 'react'; import deepEqual from 'fast-deep-equal'; import type { DataViewBase, Filter, Query, TimeRange } from '@kbn/es-query'; @@ -16,6 +16,8 @@ import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import { SearchBar } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { useKibana } from '../../lib/kibana'; + export interface QueryBarComponentProps { dataTestSubj?: string; dateRangeFrom?: string; @@ -36,6 +38,9 @@ export interface QueryBarComponentProps { isDisabled?: boolean; } +export const isDataView = (obj: unknown): obj is DataView => + obj != null && typeof obj === 'object' && Object.hasOwn(obj, 'getName'); + export const QueryBar = memo( ({ dateRangeFrom, @@ -56,6 +61,8 @@ export const QueryBar = memo( displayStyle, isDisabled, }) => { + const { data } = useKibana().services; + const [dataView, setDataView] = useState(); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { if (payload.query != null && !deepEqual(payload.query, filterQuery)) { @@ -102,16 +109,33 @@ export const QueryBar = memo( [filterManager] ); - const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); - const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); + useEffect(() => { + let dv: DataView; + if (isDataView(indexPattern)) { + setDataView(indexPattern); + } else { + const createDataView = async () => { + dv = await data.dataViews.create({ title: indexPattern.title }); + setDataView(dv); + }; + createDataView(); + } + return () => { + if (dv?.id) { + data.dataViews.clearInstanceCache(dv?.id); + } + }; + }, [data.dataViews, indexPattern]); + const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); + const arrDataView = useMemo(() => (dataView != null ? [dataView] : []), [dataView]); return ( Date: Mon, 18 Sep 2023 16:28:09 +0200 Subject: [PATCH 18/58] [Defend Workflows] Artifact rollout feature flag (#166433) This PR sets default value of `protectionUpdatesEnabled` feature flag to `false`, modifies existing `.cy` e2es to be ran with said feature flag set to true and introduces e2e coverage for scenarios when feature is disabled. --- .../common/experimental_features.ts | 2 +- .../cypress/e2e/endpoint/policy_details.cy.ts | 359 +++++++++--------- ...olicy_experimental_features_disabled.cy.ts | 98 +++++ .../cypress/e2e/endpoint/policy_list.cy.ts | 127 ++++--- 4 files changed, 349 insertions(+), 237 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_experimental_features_disabled.cy.ts diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index cbc84c3314e88..27e9f4f918cba 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -108,7 +108,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables Protection Updates tab in the Endpoint Policy Details page */ - protectionUpdatesEnabled: true, + protectionUpdatesEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts index 49bb33cbc3267..0689f89f75472 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_details.cy.ts @@ -16,220 +16,229 @@ import { import { login, ROLE } from '../../tasks/login'; import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; -describe('Policy Details', () => { - describe('Protection updates', () => { - const loadProtectionUpdatesUrl = (policyId: string) => - loadPage(`/app/security/administration/policy/${policyId}/protectionUpdates`); - const testNote = 'test note'; - const updatedTestNote = 'updated test note'; - - describe('Renders and saves protection updates', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - const today = moment.utc(); - const formattedToday = today.format('MMMM DD, YYYY'); - - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); +describe( + 'Policy Details', + { + env: { ftrConfig: { enableExperimental: ['protectionUpdatesEnabled'] } }, + }, + () => { + describe('Protection updates', () => { + const loadProtectionUpdatesUrl = (policyId: string) => + loadPage(`/app/security/administration/policy/${policyId}/protectionUpdates`); + const testNote = 'test note'; + const updatedTestNote = 'updated test note'; + + describe('Renders and saves protection updates', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + const today = moment.utc(); + const formattedToday = today.format('MMMM DD, YYYY'); + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + }); }); }); - }); - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); - it('should render the protection updates tab content', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-automatic-updates-enabled'); - cy.getByTestSubj('protection-updates-manifest-switch'); - cy.getByTestSubj('protection-updates-manifest-name-title'); - cy.getByTestSubj('protection-updates-manifest-name'); + it('should render the protection updates tab content', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-automatic-updates-enabled'); + cy.getByTestSubj('protection-updates-manifest-switch'); + cy.getByTestSubj('protection-updates-manifest-name-title'); + cy.getByTestSubj('protection-updates-manifest-name'); - cy.getByTestSubj('protection-updates-manifest-switch').click(); + cy.getByTestSubj('protection-updates-manifest-switch').click(); - cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); - cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); - cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); - cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { - cy.get('input').should('have.value', formattedToday); + cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); + cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); + cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); + cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { + cy.get('input').should('have.value', formattedToday); + }); + cy.getByTestSubj('protection-updates-manifest-name-note-title'); + cy.getByTestSubj('protection-updates-manifest-note'); + cy.getByTestSubj('policyDetailsSaveButton'); }); - cy.getByTestSubj('protection-updates-manifest-name-note-title'); - cy.getByTestSubj('protection-updates-manifest-note'); - cy.getByTestSubj('policyDetailsSaveButton'); - }); - it('should successfully update the manifest version to custom date', () => { - loadProtectionUpdatesUrl(policy.id); - 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.wait('@policy').then(({ request, response }) => { - expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - today.format('YYYY-MM-DD') - ); - expect(response?.statusCode).to.equal(200); - }); + it('should successfully update the manifest version to custom date', () => { + loadProtectionUpdatesUrl(policy.id); + 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.wait('@policy').then(({ request, response }) => { + expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( + today.format('YYYY-MM-DD') + ); + expect(response?.statusCode).to.equal(200); + }); - cy.wait('@note').then(({ request, response }) => { - expect(request.body.note).to.equal(testNote); - expect(response?.statusCode).to.equal(200); - }); + cy.wait('@note').then(({ request, response }) => { + expect(request.body.note).to.equal(testNote); + expect(response?.statusCode).to.equal(200); + }); - cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); - cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); + cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); + cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + }); }); - }); - describe('Renders and saves protection updates with custom version', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; + describe('Renders and saves protection updates with custom version', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; - const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); + const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); + }); }); }); - }); - - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); - 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'); + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); - cy.getByTestSubj('protection-updates-manifest-switch').click(); - cy.wait('@policy_latest').then(({ request, response }) => { - expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - 'latest' - ); - expect(response?.statusCode).to.equal(200); + 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('protection-updates-manifest-switch').click(); + cy.wait('@policy_latest').then(({ request, response }) => { + expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( + 'latest' + ); + expect(response?.statusCode).to.equal(200); + }); + cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); + cy.getByTestSubj('protection-updates-automatic-updates-enabled'); }); - cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-automatic-updates-enabled'); }); - }); - describe('Renders and saves protection updates with custom note', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; + describe('Renders and saves protection updates with custom note', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; - const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); + const twoMonthsAgo = moment.utc().subtract(2, 'months').format('YYYY-MM-DD'); - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); - setCustomProtectionUpdatesNote(policy.id, testNote); + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo); + setCustomProtectionUpdatesNote(policy.id, testNote); + }); }); }); - }); - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); - it('should update note on save', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); - cy.getByTestSubj('protection-updates-manifest-note').clear().type(updatedTestNote); + it('should update note on save', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); + cy.getByTestSubj('protection-updates-manifest-note').clear().type(updatedTestNote); - cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note_updated'); - cy.getByTestSubj('policyDetailsSaveButton').click(); - cy.wait('@note_updated').then(({ request, response }) => { - expect(request.body.note).to.equal(updatedTestNote); - expect(response?.statusCode).to.equal(200); + cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note_updated'); + cy.getByTestSubj('policyDetailsSaveButton').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('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-manifest-note').contains(updatedTestNote); }); - }); - describe('Renders read only protection updates for user without write permissions', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; - let policy: PolicyData; - const twoMonthsAgo = moment.utc().subtract(2, 'months'); + describe('Renders read only protection updates for user without write permissions', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + const twoMonthsAgo = moment.utc().subtract(2, 'months'); - beforeEach(() => { - login(ROLE.endpoint_security_policy_management_read); - disableExpandableFlyoutAdvancedSettings(); - }); + beforeEach(() => { + login(ROLE.endpoint_security_policy_management_read); + disableExpandableFlyoutAdvancedSettings(); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; - policy = indexedPolicy.integrationPolicies[0]; - setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo.format('YYYY-MM-DD')); - setCustomProtectionUpdatesNote(policy.id, testNote); + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + setCustomProtectionUpdatesManifestVersion( + policy.id, + twoMonthsAgo.format('YYYY-MM-DD') + ); + setCustomProtectionUpdatesNote(policy.id, testNote); + }); }); }); - }); - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the protection updates tab content', () => { + loadProtectionUpdatesUrl(policy.id); + cy.getByTestSubj('protection-updates-manifest-switch').should('not.exist'); + cy.getByTestSubj('protection-updates-state-view-mode'); + cy.getByTestSubj('protection-updates-manifest-name-title'); + cy.getByTestSubj('protection-updates-manifest-name'); - it('should render the protection updates tab content', () => { - loadProtectionUpdatesUrl(policy.id); - cy.getByTestSubj('protection-updates-manifest-switch').should('not.exist'); - cy.getByTestSubj('protection-updates-state-view-mode'); - cy.getByTestSubj('protection-updates-manifest-name-title'); - cy.getByTestSubj('protection-updates-manifest-name'); - - cy.getByTestSubj('protection-updates-manifest-name-deployed-version-title'); - cy.getByTestSubj('protection-updates-deployed-version').contains( - twoMonthsAgo.format('MMMM DD, YYYY') - ); - cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); - cy.getByTestSubj('protection-updates-version-to-deploy-view-mode'); - cy.getByTestSubj('protection-updates-version-to-deploy-picker').should('not.exist'); - - 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('protection-updates-manifest-name-deployed-version-title'); + cy.getByTestSubj('protection-updates-deployed-version').contains( + twoMonthsAgo.format('MMMM DD, YYYY') + ); + cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); + cy.getByTestSubj('protection-updates-version-to-deploy-view-mode'); + cy.getByTestSubj('protection-updates-version-to-deploy-picker').should('not.exist'); + + 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'); + }); }); }); - }); -}); + } +); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_experimental_features_disabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_experimental_features_disabled.cy.ts new file mode 100644 index 0000000000000..1d072bad8a68b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_experimental_features_disabled.cy.ts @@ -0,0 +1,98 @@ +/* + * 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 { PolicyData } from '../../../../../common/endpoint/types'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; + +import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { login } from '../../tasks/login'; +import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; + +describe('Disabled experimental features on: ', () => { + describe('Policy list', () => { + describe('Renders policy list without protection updates feature flag', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should render the list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('tableHeaderCell_Name_0'); + cy.getByTestSubj('tableHeaderCell_Deployed Version_1').should('not.exist'); + cy.getByTestSubj('tableHeaderCell_created_by_1'); + cy.getByTestSubj('tableHeaderCell_created_at_2'); + cy.getByTestSubj('tableHeaderCell_updated_by_3'); + cy.getByTestSubj('tableHeaderCell_updated_at_4'); + cy.getByTestSubj('tableHeaderCell_Endpoints_5'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('not.exist'); + cy.getByTestSubj('policyDeployedVersion').should('not.exist'); + }); + }); + }); + + describe('Policy details', () => { + describe('Renders policy details without protection updates feature flag', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; + let policy: PolicyData; + + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); + + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + policy = indexedPolicy.integrationPolicies[0]; + }); + }); + }); + + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); + + it('should return 404 on policyUpdates url', () => { + loadPage(`/app/security/administration/policy/${policy.id}/protectionUpdates`); + cy.getByTestSubj('notFoundPage'); + cy.getByTestSubj('protection-updates-automatic-updates-enabled').should('not.exist'); + }); + + it('should render policy details without protection updates tab', () => { + loadPage(`/app/security/administration/policy/${policy.id}`); + cy.get('div[role="tablist"]').within(() => { + cy.contains('Protection updates').should('not.exist'); + cy.get('#settings'); + cy.get('#trustedApps'); + cy.get('#hostIsolationExceptions'); + cy.get('#blocklists'); + cy.get('#protectionUpdates').should('not.exist'); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_list.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_list.cy.ts index 1b70ecba0882e..b3e5e0477c5be 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_list.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/policy_list.cy.ts @@ -13,83 +13,88 @@ import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/e import { login } from '../../tasks/login'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; -describe('Policy List', () => { - describe('Renders policy list with outdated policies', () => { - const indexedPolicies: IndexedFleetEndpointPolicyResponse[] = []; +describe( + 'Policy List', + { + env: { ftrConfig: { enableExperimental: ['protectionUpdatesEnabled'] } }, + }, + () => { + describe('Renders policy list with outdated policies', () => { + const indexedPolicies: IndexedFleetEndpointPolicyResponse[] = []; - const monthAgo = moment.utc().subtract(1, 'months').format('YYYY-MM-DD'); - const threeDaysAgo = moment.utc().subtract(3, 'days').format('YYYY-MM-DD'); - const eighteenMonthsAgo = moment - .utc() - .subtract(18, 'months') - .add(1, 'day') - .format('YYYY-MM-DD'); + const monthAgo = moment.utc().subtract(1, 'months').format('YYYY-MM-DD'); + const threeDaysAgo = moment.utc().subtract(3, 'days').format('YYYY-MM-DD'); + const eighteenMonthsAgo = moment + .utc() + .subtract(18, 'months') + .add(1, 'day') + .format('YYYY-MM-DD'); + const dates = [monthAgo, threeDaysAgo, eighteenMonthsAgo]; - const dates = [monthAgo, threeDaysAgo, eighteenMonthsAgo]; + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); + before(() => { + getEndpointIntegrationVersion().then((version) => { + for (let i = 0; i < 4; i++) { + createAgentPolicyTask(version).then((data) => { + indexedPolicies.push(data); + if (dates[i]) { + setCustomProtectionUpdatesManifestVersion(data.integrationPolicies[0].id, dates[i]); + } + }); + } + }); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - for (let i = 0; i < 4; i++) { - createAgentPolicyTask(version).then((data) => { - indexedPolicies.push(data); - if (dates[i]) { - setCustomProtectionUpdatesManifestVersion(data.integrationPolicies[0].id, dates[i]); - } + after(() => { + if (indexedPolicies.length) { + indexedPolicies.forEach((policy) => { + cy.task('deleteIndexedFleetEndpointPolicies', policy); }); } }); - }); - after(() => { - if (indexedPolicies.length) { - indexedPolicies.forEach((policy) => { - cy.task('deleteIndexedFleetEndpointPolicies', policy); + it('should render the policy list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('contain', '2 policies'); + dates.forEach((date) => { + cy.contains(moment.utc(date, 'YYYY-MM-DD').format('MMMM DD, YYYY')); }); - } - }); - - it('should render the policy list', () => { - loadPage('/app/security/administration/policy'); - cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('contain', '2 policies'); - dates.forEach((date) => { - cy.contains(moment.utc(date, 'YYYY-MM-DD').format('MMMM DD, YYYY')); + cy.getByTestSubj('policyDeployedVersion').should('have.length', 4); }); - cy.getByTestSubj('policyDeployedVersion').should('have.length', 4); }); - }); - describe('Renders policy list with no outdated policies', () => { - let indexedPolicy: IndexedFleetEndpointPolicyResponse; + describe('Renders policy list with no outdated policies', () => { + let indexedPolicy: IndexedFleetEndpointPolicyResponse; - beforeEach(() => { - login(); - disableExpandableFlyoutAdvancedSettings(); - }); + beforeEach(() => { + login(); + disableExpandableFlyoutAdvancedSettings(); + }); - before(() => { - getEndpointIntegrationVersion().then((version) => { - createAgentPolicyTask(version).then((data) => { - indexedPolicy = data; + before(() => { + getEndpointIntegrationVersion().then((version) => { + createAgentPolicyTask(version).then((data) => { + indexedPolicy = data; + }); }); }); - }); - after(() => { - if (indexedPolicy) { - cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); - } - }); + after(() => { + if (indexedPolicy) { + cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy); + } + }); - it('should render the list', () => { - loadPage('/app/security/administration/policy'); - cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('not.exist'); - cy.getByTestSubj('policyDeployedVersion').should('have.length', 1); - cy.getByTestSubj('policyDeployedVersion').should('have.text', 'latest'); + it('should render the list', () => { + loadPage('/app/security/administration/policy'); + cy.getByTestSubj('policy-list-outdated-manifests-call-out').should('not.exist'); + cy.getByTestSubj('policyDeployedVersion').should('have.length', 1); + cy.getByTestSubj('policyDeployedVersion').should('have.text', 'latest'); + }); }); - }); -}); + } +); From 05e0666d23a93d4bbce4438b0b99f5d364d6092a Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 18 Sep 2023 16:39:57 +0200 Subject: [PATCH 19/58] [Serverless] Show project name in the header (#166442) > [!IMPORTANT] > I plan to merge this as an intermediate state. The next step is changing the breadcrumbs component and make the project name as part of it https://github.com/elastic/kibana/issues/166593 ## Summary close https://github.com/elastic/kibana/issues/166182 Shows project name in the Kibana header. To test locally add to the `config/serverless.yml`: ``` xpack.cloud.serverless.project_id: "random" xpack.cloud.serverless.project_name: "My Search Project" ``` ![Screenshot 2023-09-14 at 13 01 44](https://github.com/elastic/kibana/assets/7784120/4c658e19-5509-4a56-8752-a3a0f677d454) I hardcoded 320px max-width to enable truncation for longer titles: ![Screenshot 2023-09-14 at 13 02 11](https://github.com/elastic/kibana/assets/7784120/4cab9643-3eb2-4d50-a737-66bb98a46109) In general, the header is not very flexible and has issues on smaller screen, but this needs to be fixed separately. The link still leads to the `/projects` page of the cloud UI --- .../src/chrome_service.tsx | 7 +++++++ .../project_navigation_service.ts | 7 +++++++ .../core-chrome-browser-internal/src/types.ts | 6 ++++++ .../src/ui/project/header.test.tsx | 2 ++ .../src/ui/project/header.tsx | 16 ++++++++++++++-- .../src/chrome_service.mock.ts | 1 + x-pack/plugins/serverless/public/plugin.tsx | 3 +++ 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 02ba5a912cd40..47abc6c5646fe 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -278,6 +278,11 @@ export class ChromeService { projectNavigation.setProjectsUrl(projectsUrl); }; + const setProjectName = (projectName: string) => { + validateChromeStyle(); + projectNavigation.setProjectName(projectName); + }; + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -371,6 +376,7 @@ export class ChromeService { headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))} homeHref$={projectNavigation.getProjectHome$()} projectsUrl$={projectNavigation.getProjectsUrl$()} + projectName$={projectNavigation.getProjectName$()} docLinks={docLinks} kibanaVersion={injectedMetadata.getKibanaVersion()} prependBasePath={http.basePath.prepend} @@ -499,6 +505,7 @@ export class ChromeService { project: { setHome: setProjectHome, setProjectsUrl, + setProjectName, setNavigation: setProjectNavigation, setSideNavComponent: setProjectSideNavComponent, setBreadcrumbs: setProjectBreadcrumbs, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 90be8ff754053..65d7fcc1bf559 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -46,6 +46,7 @@ export class ProjectNavigationService { }>({ current: null }); private projectHome$ = new BehaviorSubject(undefined); private projectsUrl$ = new BehaviorSubject(undefined); + private projectName$ = new BehaviorSubject(undefined); private projectNavigation$ = new BehaviorSubject(undefined); private activeNodes$ = new BehaviorSubject([]); private projectNavigationNavTreeFlattened: Record = {}; @@ -98,6 +99,12 @@ export class ProjectNavigationService { getProjectsUrl$: () => { return this.projectsUrl$.asObservable(); }, + setProjectName: (projectName: string) => { + this.projectName$.next(projectName); + }, + getProjectName$: () => { + return this.projectName$.asObservable(); + }, setProjectNavigation: (projectNavigation: ChromeProjectNavigation) => { this.projectNavigation$.next(projectNavigation); this.projectNavigationNavTreeFlattened = flattenNav(projectNavigation.navigationTree); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/types.ts b/packages/core/chrome/core-chrome-browser-internal/src/types.ts index 1eea86ad4090d..009aa57ee6b3b 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/types.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/types.ts @@ -50,6 +50,12 @@ export interface InternalChromeStart extends ChromeStart { */ setProjectsUrl(projectsUrl: string): void; + /** + * Sets the project name. + * @param projectName + */ + setProjectName(projectName: string): void; + /** * Sets the project navigation config to be used for rendering project navigation. * It is used for default project sidenav, project breadcrumbs, tracking active deep link. diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx index b2a1cecede239..167b11629ce55 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx @@ -29,6 +29,7 @@ describe('Header', () => { helpMenuLinks$: Rx.of([]), homeHref$: Rx.of('app/home'), projectsUrl$: Rx.of('/projects/'), + projectName$: Rx.of('My Project'), kibanaVersion: '8.9', loadingCount$: Rx.of(0), navControlsLeft$: Rx.of([]), @@ -82,5 +83,6 @@ describe('Header', () => { const projectsLink = await screen.getByTestId('projectsLink'); expect(projectsLink).toHaveAttribute('href', '/projects/'); + expect(projectsLink).toHaveTextContent('My Project'); }); }); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 239b8487e1a06..1cbf8eaa9af0a 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -73,6 +73,12 @@ const getHeaderCss = ({ size }: EuiThemeComputed) => ({ padding-right: ${size.xs}; `, }, + projectName: { + link: css` + /* TODO: make header layout more flexible? */ + max-width: 320px; + `, + }, }); type HeaderCss = ReturnType; @@ -107,6 +113,7 @@ export interface Props { helpMenuLinks$: Observable; homeHref$: Observable; projectsUrl$: Observable; + projectName$: Observable; kibanaVersion: string; application: InternalApplicationStart; loadingCount$: ReturnType; @@ -184,6 +191,7 @@ export const ProjectHeader = ({ const toggleCollapsibleNavRef = createRef void }>(); const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$); const projectsUrl = useObservable(observables.projectsUrl$); + const projectName = useObservable(observables.projectName$); const { euiTheme } = useEuiTheme(); const headerCss = getHeaderCss(euiTheme); const { logo: logoCss } = headerCss; @@ -246,8 +254,12 @@ export const ProjectHeader = ({ - - {headerStrings.cloud.linkToProjects} + + {projectName ?? headerStrings.cloud.linkToProjects} diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 884426a7f037a..225263674e595 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -70,6 +70,7 @@ const createStartContractMock = () => { project: { setHome: jest.fn(), setProjectsUrl: jest.fn(), + setProjectName: jest.fn(), setNavigation: jest.fn(), setSideNavComponent: jest.fn(), setBreadcrumbs: jest.fn(), diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index d49447e4d36dd..2b2c11001e8f3 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -68,6 +68,9 @@ export class ServerlessPlugin if (dependencies.cloud.projectsUrl) { project.setProjectsUrl(dependencies.cloud.projectsUrl); } + if (dependencies.cloud.serverless.projectName) { + project.setProjectName(dependencies.cloud.serverless.projectName); + } return { setSideNavComponent: (sideNavigationComponent) => From d8c112e9b73b8a02b8917bb7237cab8595d7ff82 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Mon, 18 Sep 2023 11:41:12 -0400 Subject: [PATCH 20/58] [Serverless] Unify Dashboard app IDs in functional tests (#166377) Removes all usages of `PageObjects.common.navigateToApp('dashboard')` in favour of a Dashboard page method. --- src/plugins/dashboard/public/plugin.tsx | 4 ++-- ...rd_dark.svg => kibana_dashboards_dark.svg} | 0 ..._light.svg => kibana_dashboards_light.svg} | 0 .../public/components/overview/overview.tsx | 2 +- .../apps/context/_discover_navigation.ts | 2 +- .../context/classic/_discover_navigation.ts | 2 +- .../group1/create_and_add_embeddables.ts | 4 ++-- .../group1/dashboard_unsaved_listing.ts | 2 +- .../group1/dashboard_unsaved_state.ts | 6 ++--- .../group1/edit_embeddable_redirects.ts | 4 ++-- .../dashboard/group1/edit_visualizations.js | 6 ++--- .../dashboard/group1/embeddable_data_grid.ts | 2 +- .../dashboard/group1/embeddable_rendering.ts | 2 +- .../dashboard/group1/url_field_formatter.ts | 2 +- .../dashboard/group2/dashboard_filter_bar.ts | 2 +- .../dashboard/group2/dashboard_filtering.ts | 2 +- .../apps/dashboard/group2/full_screen_mode.ts | 2 +- .../dashboard/group2/panel_expand_toggle.ts | 2 +- .../apps/dashboard/group3/copy_panel_to.ts | 2 +- .../apps/dashboard/group4/dashboard_empty.ts | 4 ++-- .../apps/dashboard/group4/dashboard_time.ts | 2 +- .../dashboard/group5/dashboard_back_button.ts | 2 +- .../group5/dashboard_error_handling.ts | 2 +- .../dashboard/group5/dashboard_query_bar.ts | 2 +- .../dashboard/group5/dashboard_settings.ts | 2 +- .../group5/data_shared_attributes.ts | 2 +- .../apps/dashboard/group5/embed_mode.ts | 2 +- .../apps/dashboard/group5/empty_dashboard.ts | 2 +- .../apps/dashboard/group5/legacy_urls.ts | 4 ++-- .../group5/saved_search_embeddable.ts | 2 +- .../functional/apps/dashboard/group5/share.ts | 2 +- .../apps/dashboard/group6/dashboard_grid.ts | 2 +- .../dashboard/group6/dashboard_saved_query.ts | 2 +- .../dashboard/group6/dashboard_snapshots.ts | 2 +- .../dashboard/group6/embeddable_library.ts | 2 +- .../apps/dashboard/group6/view_edit.ts | 2 +- .../controls/common/control_group_chaining.ts | 2 +- .../controls/common/control_group_settings.ts | 8 ++----- .../controls/common/index.ts | 10 +++------ .../controls/common/range_slider.ts | 4 ++-- .../controls/common/replace_controls.ts | 2 +- .../controls/common/time_slider.ts | 7 +++--- .../controls/options_list/index.ts | 4 ++-- ...ptions_list_allow_expensive_queries_off.ts | 2 +- .../options_list_dashboard_interaction.ts | 2 +- .../image_embeddable/image_embeddable.ts | 2 +- .../embeddable/_saved_search_embeddable.ts | 2 +- .../discover/group1/_discover_histogram.ts | 11 ++++++++-- .../apps/discover/group2/_adhoc_data_views.ts | 2 +- .../apps/discover/group2/_chart_hidden.ts | 4 ++-- .../discover/group2/_data_grid_context.ts | 2 +- .../discover/group2/_data_grid_doc_table.ts | 2 +- .../apps/kibana_overview/_analytics.ts | 2 +- .../apps/management/_kibana_settings.ts | 4 ++-- .../group1/_data_table_notimeindex_filters.ts | 2 +- .../visualize/group3/_add_to_dashboard.ts | 8 +++---- .../visualize/group5/_tsvb_time_series.ts | 2 +- .../functional/page_objects/dashboard_page.ts | 8 ++++++- .../page_objects/time_to_visualize_page.ts | 2 +- .../feature_controls/dashboard_security.ts | 4 ++-- .../time_to_visualize_security.ts | 6 ++--- .../apps/dashboard/group1/preserve_url.ts | 4 ++-- .../apps/dashboard/group2/_async_dashboard.ts | 2 +- .../group2/dashboard_lens_by_value.ts | 2 +- .../group2/dashboard_maps_by_value.ts | 2 +- .../group2/dashboard_search_by_value.ts | 2 +- .../controls_migration_smoke_test.ts | 5 ++--- .../lens_migration_smoke_test.ts | 2 +- .../tsvb_migration_smoke_test.ts | 4 ++-- .../visualize_migration_smoke_test.ts | 2 +- .../apps/dashboard/group2/panel_time_range.ts | 2 +- .../apps/dashboard/group2/panel_titles.ts | 2 +- .../apps/dashboard/group2/sync_colors.ts | 2 +- .../dashboard_to_dashboard_drilldown.ts | 4 ++-- .../drilldowns/dashboard_to_url_drilldown.ts | 2 +- .../drilldowns/explore_data_chart_action.ts | 4 ++-- .../drilldowns/explore_data_panel_action.ts | 8 +++---- .../group3/reporting/download_csv.ts | 2 +- .../dashboard/group3/reporting/screenshots.ts | 10 ++++----- .../apps/discover/async_scripted_fields.js | 2 +- .../apps/discover/saved_searches.ts | 2 +- .../apps/lens/group3/add_to_dashboard.ts | 2 +- .../functional/apps/lens/group4/dashboard.ts | 22 +++++++++---------- .../apps/lens/group6/error_handling.ts | 4 ++-- .../apps/lens/group6/lens_reporting.ts | 2 +- .../apps/lens/group6/lens_tagging.ts | 2 +- .../group2/embeddable/add_to_dashboard.js | 8 +++---- .../apps/maps/group2/embeddable/dashboard.js | 4 ++-- .../group2/embeddable/embeddable_library.js | 2 +- .../group2/embeddable/embeddable_state.js | 2 +- .../group2/embeddable/filter_by_map_extent.js | 2 +- .../maps/group2/embeddable/save_and_return.js | 4 ++-- .../embeddable/tooltip_filter_actions.js | 2 +- .../apps/maps/group3/reports/index.ts | 4 ++-- .../anomaly_charts_dashboard_embeddables.ts | 2 +- .../anomaly_embeddables_migration.ts | 4 ++-- .../lens_to_ml.ts | 2 +- .../lens_to_ml_with_wizard.ts | 2 +- .../map_to_ml.ts | 2 +- ...index_data_visualizer_grid_in_dashboard.ts | 4 ++-- .../import_saved_objects_between_versions.ts | 2 +- .../apps/spaces/spaces_selection.ts | 2 +- .../functional/apps/visualize/telemetry.ts | 2 +- 103 files changed, 170 insertions(+), 167 deletions(-) rename src/plugins/kibana_overview/public/assets/{kibana_dashboard_dark.svg => kibana_dashboards_dark.svg} (100%) rename src/plugins/kibana_overview/public/assets/{kibana_dashboard_light.svg => kibana_dashboards_light.svg} (100%) diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index d2802a8ef3b6d..4d46b837da5ca 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -282,7 +282,7 @@ export class DashboardPlugin if (home) { home.featureCatalogue.register({ - id: LEGACY_DASHBOARD_APP_ID, + id: DASHBOARD_APP_ID, title: dashboardAppTitle, subtitle: i18n.translate('dashboard.featureCatalogue.dashboardSubtitle', { defaultMessage: 'Analyze data in dashboards.', @@ -291,7 +291,7 @@ export class DashboardPlugin defaultMessage: 'Display and share a collection of visualizations and saved searches.', }), icon: 'dashboardApp', - path: `/app/dashboards#${LANDING_PAGE_PATH}`, + path: `/app/${DASHBOARD_APP_ID}#${LANDING_PAGE_PATH}`, showOnHomePage: false, category: 'data', solutionId: 'kibana', diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboards_dark.svg similarity index 100% rename from src/plugins/kibana_overview/public/assets/kibana_dashboard_dark.svg rename to src/plugins/kibana_overview/public/assets/kibana_dashboards_dark.svg diff --git a/src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg b/src/plugins/kibana_overview/public/assets/kibana_dashboards_light.svg similarity index 100% rename from src/plugins/kibana_overview/public/assets/kibana_dashboard_light.svg rename to src/plugins/kibana_overview/public/assets/kibana_dashboards_light.svg diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index f6d97d54681e8..0df0e583699f7 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -162,7 +162,7 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => }; // Dashboard and discover are displayed in larger cards - const mainApps = ['dashboard', 'discover']; + const mainApps = ['dashboards', 'discover']; const remainingApps = kibanaApps.map(({ id }) => id).filter((id) => !mainApps.includes(id)); const onDataViewCreated = () => { diff --git a/test/functional/apps/context/_discover_navigation.ts b/test/functional/apps/context/_discover_navigation.ts index 99e27b52983a0..54c743df37309 100644 --- a/test/functional/apps/context/_discover_navigation.ts +++ b/test/functional/apps/context/_discover_navigation.ts @@ -134,7 +134,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/context/classic/_discover_navigation.ts b/test/functional/apps/context/classic/_discover_navigation.ts index b0b7f672a75b3..d20de752061e9 100644 --- a/test/functional/apps/context/classic/_discover_navigation.ts +++ b/test/functional/apps/context/classic/_discover_navigation.ts @@ -136,7 +136,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my classic search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts b/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts index bc79023bd51b7..9531606e649f3 100644 --- a/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts +++ b/test/functional/apps/dashboard/group1/create_and_add_embeddables.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('ensure toolbar popover closes on add', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.switchToEditMode(); await dashboardAddPanel.clickEditorMenuButton(); @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('add new visualization link', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts index 73e97aab3cb36..87c19402a51be 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_listing.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts index 51669b395b6f1..902e76342ec98 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -71,7 +71,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.waitForRenderComplete(); await validateQueryAndFilter(); @@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('few panels'); const currentPanelCount = await PageObjects.dashboard.getPanelCount(); expect(currentPanelCount).to.eql(unsavedPanelCount); diff --git a/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts b/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts index 7ed4cba6c7089..f11ca330bf178 100644 --- a/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts +++ b/test/functional/apps/dashboard/group1/edit_embeddable_redirects.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); @@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { redirectToOrigin: false, }); await PageObjects.visualize.notLinkedToOriginatingApp(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); it('loses originatingApp connection after first save when redirectToOrigin is false', async () => { diff --git a/test/functional/apps/dashboard/group1/edit_visualizations.js b/test/functional/apps/dashboard/group1/edit_visualizations.js index d4de54586b731..20234ca1f8055 100644 --- a/test/functional/apps/dashboard/group1/edit_visualizations.js +++ b/test/functional/apps/dashboard/group1/edit_visualizations.js @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }) { }); it('visualize app menu navigates to the visualize listing page if the last opened visualization was linked to dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); @@ -133,7 +133,7 @@ export default function ({ getService, getPageObjects }) { describe('by value', () => { it('save and return button returns to dashboard after editing visualization with changes saved', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await createMarkdownVis(); diff --git a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts index 85277e63d6f6c..71da8fadea4af 100644 --- a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts +++ b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', 'doc_table:legacy': false, }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group1/embeddable_rendering.ts b/test/functional/apps/dashboard/group1/embeddable_rendering.ts index d7addf89ac404..45408a8846c17 100644 --- a/test/functional/apps/dashboard/group1/embeddable_rendering.ts +++ b/test/functional/apps/dashboard/group1/embeddable_rendering.ts @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await elasticChart.setNewChartUiDebugFlag(true); diff --git a/test/functional/apps/dashboard/group1/url_field_formatter.ts b/test/functional/apps/dashboard/group1/url_field_formatter.ts index 688499a36e34c..d2a4be0598a35 100644 --- a/test/functional/apps/dashboard/group1/url_field_formatter.ts +++ b/test/functional/apps/dashboard/group1/url_field_formatter.ts @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('applied on dashboard', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('dashboard with table'); await dashboard.waitForRenderComplete(); const fieldLink = await visChart.getFieldLinkInVisTable(`${fieldName}: Descending`); diff --git a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts index 1244e179f7f6a..da9660ac4f4cb 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group2/dashboard_filtering.ts b/test/functional/apps/dashboard/group2/dashboard_filtering.ts index 1fb70ef508c2b..24f276b831036 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filtering.ts @@ -66,7 +66,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.gotoDashboardLandingPage(); }); diff --git a/test/functional/apps/dashboard/group2/full_screen_mode.ts b/test/functional/apps/dashboard/group2/full_screen_mode.ts index 53cb707961ea6..23be5e4b7afe6 100644 --- a/test/functional/apps/dashboard/group2/full_screen_mode.ts +++ b/test/functional/apps/dashboard/group2/full_screen_mode.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group2/panel_expand_toggle.ts b/test/functional/apps/dashboard/group2/panel_expand_toggle.ts index f33280ba7bb79..99d09a5f42e7e 100644 --- a/test/functional/apps/dashboard/group2/panel_expand_toggle.ts +++ b/test/functional/apps/dashboard/group2/panel_expand_toggle.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); }); diff --git a/test/functional/apps/dashboard/group3/copy_panel_to.ts b/test/functional/apps/dashboard/group3/copy_panel_to.ts index 1f40f780a5398..81c5406426127 100644 --- a/test/functional/apps/dashboard/group3/copy_panel_to.ts +++ b/test/functional/apps/dashboard/group3/copy_panel_to.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard(fewPanelsTitle); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group4/dashboard_empty.ts b/test/functional/apps/dashboard/group4/dashboard_empty.ts index 02437b0685694..03a9d965d589b 100644 --- a/test/functional/apps/dashboard/group4/dashboard_empty.ts +++ b/test/functional/apps/dashboard/group4/dashboard_empty.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); log.debug('load kibana with no data'); await kibanaServer.importExport.unload(kbnDirectory); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); // create the new data view from the dashboards/create route in order to test that the dashboard is loaded properly as soon as the data view is created... - await PageObjects.common.navigateToApp('dashboard', { hash: '/create' }); + await PageObjects.common.navigateToApp('dashboards', { hash: '/create' }); const button = await testSubjects.find('createDataViewButton'); button.click(); diff --git a/test/functional/apps/dashboard/group4/dashboard_time.ts b/test/functional/apps/dashboard/group4/dashboard_time.ts index e1dbefa63ac74..2b35c5e78f331 100644 --- a/test/functional/apps/dashboard/group4/dashboard_time.ts +++ b/test/functional/apps/dashboard/group4/dashboard_time.ts @@ -24,7 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); describe('dashboard without stored timed', () => { diff --git a/test/functional/apps/dashboard/group5/dashboard_back_button.ts b/test/functional/apps/dashboard/group5/dashboard_back_button.ts index 1fd9614d2421a..c8fbf1c6da411 100644 --- a/test/functional/apps/dashboard/group5/dashboard_back_button.ts +++ b/test/functional/apps/dashboard/group5/dashboard_back_button.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard/group5/dashboard_error_handling.ts b/test/functional/apps/dashboard/group5/dashboard_error_handling.ts index a3265fdcc7f9d..ab8e8ac76f85b 100644 --- a/test/functional/apps/dashboard/group5/dashboard_error_handling.ts +++ b/test/functional/apps/dashboard/group5/dashboard_error_handling.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/dashboard_error_cases.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group5/dashboard_query_bar.ts b/test/functional/apps/dashboard/group5/dashboard_query_bar.ts index 010aec9607816..a3bfcea2eaa2a 100644 --- a/test/functional/apps/dashboard/group5/dashboard_query_bar.ts +++ b/test/functional/apps/dashboard/group5/dashboard_query_bar.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with filter'); }); diff --git a/test/functional/apps/dashboard/group5/dashboard_settings.ts b/test/functional/apps/dashboard/group5/dashboard_settings.ts index bbfb867cdf176..ae0e727814eef 100644 --- a/test/functional/apps/dashboard/group5/dashboard_settings.ts +++ b/test/functional/apps/dashboard/group5/dashboard_settings.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group5/data_shared_attributes.ts b/test/functional/apps/dashboard/group5/data_shared_attributes.ts index 71d8a16b2f7d8..3202d418bd512 100644 --- a/test/functional/apps/dashboard/group5/data_shared_attributes.ts +++ b/test/functional/apps/dashboard/group5/data_shared_attributes.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with everything'); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group5/embed_mode.ts b/test/functional/apps/dashboard/group5/embed_mode.ts index 16934fc9101a8..3c2cfbae77a9f 100644 --- a/test/functional/apps/dashboard/group5/embed_mode.ts +++ b/test/functional/apps/dashboard/group5/embed_mode.ts @@ -49,7 +49,7 @@ export default function ({ await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dashboard with everything'); diff --git a/test/functional/apps/dashboard/group5/empty_dashboard.ts b/test/functional/apps/dashboard/group5/empty_dashboard.ts index 49fa50c075427..6939833a80086 100644 --- a/test/functional/apps/dashboard/group5/empty_dashboard.ts +++ b/test/functional/apps/dashboard/group5/empty_dashboard.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/test/functional/apps/dashboard/group5/legacy_urls.ts b/test/functional/apps/dashboard/group5/legacy_urls.ts index 54834bf8969b7..03dabfe87ba2f 100644 --- a/test/functional/apps/dashboard/group5/legacy_urls.ts +++ b/test/functional/apps/dashboard/group5/legacy_urls.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie'); await PageObjects.dashboard.saveDashboard('legacyTest', { waitDialogIsClosed: true }); @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('resolves markdown link from dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization('legacy url markdown'); (await find.byLinkText('abc')).click(); diff --git a/test/functional/apps/dashboard/group5/saved_search_embeddable.ts b/test/functional/apps/dashboard/group5/saved_search_embeddable.ts index c409f3d802276..f20172d10ed5c 100644 --- a/test/functional/apps/dashboard/group5/saved_search_embeddable.ts +++ b/test/functional/apps/dashboard/group5/saved_search_embeddable.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group5/share.ts b/test/functional/apps/dashboard/group5/share.ts index a6d9289313e62..45bb5cd80c508 100644 --- a/test/functional/apps/dashboard/group5/share.ts +++ b/test/functional/apps/dashboard/group5/share.ts @@ -109,7 +109,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const from = 'Sep 19, 2017 @ 06:31:44.000'; const to = 'Sep 23, 2018 @ 18:31:44.000'; await PageObjects.common.setTime({ from, to }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group6/dashboard_grid.ts b/test/functional/apps/dashboard/group6/dashboard_grid.ts index 90e2187e19eb4..c48a2973acc46 100644 --- a/test/functional/apps/dashboard/group6/dashboard_grid.ts +++ b/test/functional/apps/dashboard/group6/dashboard_grid.ts @@ -25,7 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); await PageObjects.dashboard.switchToEditMode(); diff --git a/test/functional/apps/dashboard/group6/dashboard_saved_query.ts b/test/functional/apps/dashboard/group6/dashboard_saved_query.ts index 1dad54234e8a3..5b00fe2caa01b 100644 --- a/test/functional/apps/dashboard/group6/dashboard_saved_query.ts +++ b/test/functional/apps/dashboard/group6/dashboard_saved_query.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/test/functional/apps/dashboard/group6/dashboard_snapshots.ts b/test/functional/apps/dashboard/group6/dashboard_snapshots.ts index 99f7a7fe2654a..401c1fa649006 100644 --- a/test/functional/apps/dashboard/group6/dashboard_snapshots.ts +++ b/test/functional/apps/dashboard/group6/dashboard_snapshots.ts @@ -43,7 +43,7 @@ export default function ({ await browser.setScreenshotSize(1000, 500); // adding this navigate adds the timestamp hash to the url which invalidates previous // session. If we don't do this, the colors on the visualizations are different and the screenshots won't match. - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async function () { diff --git a/test/functional/apps/dashboard/group6/embeddable_library.ts b/test/functional/apps/dashboard/group6/embeddable_library.ts index 472a2a890c978..55e7ae7cee13a 100644 --- a/test/functional/apps/dashboard/group6/embeddable_library.ts +++ b/test/functional/apps/dashboard/group6/embeddable_library.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/test/functional/apps/dashboard/group6/view_edit.ts b/test/functional/apps/dashboard/group6/view_edit.ts index dfd62eeaa6cb3..a2ef56700357f 100644 --- a/test/functional/apps/dashboard/group6/view_edit.ts +++ b/test/functional/apps/dashboard/group6/view_edit.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts b/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts index 55d4e160a2e9c..7696b6a6f4762 100644 --- a/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts +++ b/test/functional/apps/dashboard_elements/controls/common/control_group_chaining.ts @@ -55,7 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); /* then, create our testing dashboard */ - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts b/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts index e07d335236f79..aa3ae89015c5e 100644 --- a/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts +++ b/test/functional/apps/dashboard_elements/controls/common/control_group_settings.ts @@ -14,15 +14,11 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); - const { dashboardControls, common, dashboard } = getPageObjects([ - 'dashboardControls', - 'dashboard', - 'common', - ]); + const { dashboardControls, dashboard } = getPageObjects(['dashboardControls', 'dashboard']); describe('Dashboard control group settings', () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await dashboard.saveDashboard('Test Control Group Settings'); diff --git a/test/functional/apps/dashboard_elements/controls/common/index.ts b/test/functional/apps/dashboard_elements/controls/common/index.ts index 99bd616de4607..8a9a7b8a54834 100644 --- a/test/functional/apps/dashboard_elements/controls/common/index.ts +++ b/test/functional/apps/dashboard_elements/controls/common/index.ts @@ -13,11 +13,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const { dashboardControls, common, dashboard } = getPageObjects([ - 'dashboardControls', - 'dashboard', - 'common', - ]); + const { dashboardControls, dashboard } = getPageObjects(['dashboardControls', 'dashboard']); async function setup() { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); @@ -31,9 +27,9 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid }); // enable the controls lab and navigate to the dashboard listing page to start - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); } diff --git a/test/functional/apps/dashboard_elements/controls/common/range_slider.ts b/test/functional/apps/dashboard_elements/controls/common/range_slider.ts index 21bdb4f897603..b97acde63f40b 100644 --- a/test/functional/apps/dashboard_elements/controls/common/range_slider.ts +++ b/test/functional/apps/dashboard_elements/controls/common/range_slider.ts @@ -49,9 +49,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { from: 'Oct 22, 2018 @ 00:00:00.000', to: 'Dec 3, 2018 @ 00:00:00.000', }); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts b/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts index dbc8682caf63e..5c065f7f000f5 100644 --- a/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts +++ b/test/functional/apps/dashboard_elements/controls/common/replace_controls.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let controlId: string; before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader', 'animals']); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard_elements/controls/common/time_slider.ts b/test/functional/apps/dashboard_elements/controls/common/time_slider.ts index 7e266376f8e74..520ef6560735f 100644 --- a/test/functional/apps/dashboard_elements/controls/common/time_slider.ts +++ b/test/functional/apps/dashboard_elements/controls/common/time_slider.ts @@ -17,12 +17,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); - const { dashboardControls, discover, timePicker, common, dashboard } = getPageObjects([ + const { dashboardControls, discover, timePicker, dashboard } = getPageObjects([ 'dashboardControls', 'discover', 'timePicker', 'dashboard', - 'common', ]); describe('Time Slider Control', async () => { @@ -55,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('create, edit, and delete', async () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); @@ -132,7 +131,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('panel interactions', async () => { describe('saved search', async () => { before(async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('timeslider and saved search'); await dashboard.waitForRenderComplete(); }); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/index.ts b/test/functional/apps/dashboard_elements/controls/options_list/index.ts index 2e1d803349936..966dbd91b7705 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/index.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/index.ts @@ -16,7 +16,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const { timePicker, dashboard, common } = getPageObjects(['timePicker', 'dashboard', 'common']); + const { timePicker, dashboard } = getPageObjects(['timePicker', 'dashboard']); const setup = async () => { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); @@ -29,7 +29,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts b/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts index 84af82f349713..91774aee02f2d 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/options_list_allow_expensive_queries_off.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await setAllowExpensiveQueries(false); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.clickNewDashboard(); await dashboard.ensureDashboardIsInEditMode(); await timePicker.setDefaultDataRange(); diff --git a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts index fd506429ba54f..16d0b2cd5fc2d 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/options_list_dashboard_interaction.ts @@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let controlId: string; const returnToDashboard = async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await header.waitUntilLoadingHasFinished(); await elasticChart.setNewChartUiDebugFlag(); await dashboard.loadSavedDashboard(OPTIONS_LIST_DASHBOARD_NAME); diff --git a/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts b/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts index caf229e8335bb..6fc586fb35bba 100644 --- a/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts +++ b/test/functional/apps/dashboard_elements/image_embeddable/image_embeddable.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.switchToEditMode(); }); diff --git a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts index 8961af57a4ad2..2fb99d9ebb43f 100644 --- a/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts +++ b/test/functional/apps/discover/embeddable/_saved_search_embeddable.ts @@ -44,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index db83f764ce6f5..6e30f75f90a10 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -15,7 +15,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const elasticChart = getService('elasticChart'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'timePicker', + 'dashboard', + 'settings', + 'discover', + 'common', + 'header', + ]); const defaultSettings = { defaultIndex: 'long-window-logstash-*', 'dateFormat:tz': 'Europe/Berlin', @@ -253,7 +260,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // go to dashboard - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.header.waitUntilLoadingHasFinished(); // go to discover diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index 299efefc12fb8..04880c1cfbc72 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -170,7 +170,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); // open searches on dashboard - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group2/_chart_hidden.ts b/test/functional/apps/discover/group2/_chart_hidden.ts index 40f50a73ac4df..6bee290df896d 100644 --- a/test/functional/apps/discover/group2/_chart_hidden.ts +++ b/test/functional/apps/discover/group2/_chart_hidden.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker', 'dashboard']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.toggleChartVisibility(); expect(await PageObjects.discover.isChartVisible()).to.be(false); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/discover/group2/_data_grid_context.ts b/test/functional/apps/discover/group2/_data_grid_context.ts index 4d90acab5eebb..ae5030f226b82 100644 --- a/test/functional/apps/discover/group2/_data_grid_context.ts +++ b/test/functional/apps/discover/group2/_data_grid_context.ts @@ -102,7 +102,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/discover/group2/_data_grid_doc_table.ts b/test/functional/apps/discover/group2/_data_grid_doc_table.ts index 5a60d6cf1f98c..5aedb67b6d5e7 100644 --- a/test/functional/apps/discover/group2/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/group2/_data_grid_doc_table.ts @@ -120,7 +120,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.saveSearch('expand-cell-search'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/kibana_overview/_analytics.ts b/test/functional/apps/kibana_overview/_analytics.ts index 7b9d352e60e31..1c4897d5830ac 100644 --- a/test/functional/apps/kibana_overview/_analytics.ts +++ b/test/functional/apps/kibana_overview/_analytics.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - const apps = ['dashboard', 'discover', 'canvas', 'maps', 'ml']; + const apps = ['dashboards', 'discover', 'canvas', 'maps', 'ml']; it('should display Analytics apps cards', async () => { const kbnOverviewAppsCards = await find.allByCssSelector('.kbnOverviewApps__item'); diff --git a/test/functional/apps/management/_kibana_settings.ts b/test/functional/apps/management/_kibana_settings.ts index d459643849fbc..875635cbd6a09 100644 --- a/test/functional/apps/management/_kibana_settings.ts +++ b/test/functional/apps/management/_kibana_settings.ts @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('when false, dashboard state is unhashed', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); const globalState = await getStateFromUrl(); @@ -73,7 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('when true, dashboard state is hashed', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); const globalState = await getStateFromUrl(); diff --git a/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts index d62bdd86ecf9b..50f855056c6cd 100644 --- a/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts +++ b/test/functional/apps/visualize/group1/_data_table_notimeindex_filters.ts @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // test to cover bug #54548 - add this visualization to a dashboard and filter it('should add to dashboard and allow filtering', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addVisualization(vizName1); diff --git a/test/functional/apps/visualize/group3/_add_to_dashboard.ts b/test/functional/apps/visualize/group3/_add_to_dashboard.ts index 896826c2caa07..ad14c89b1e830 100644 --- a/test/functional/apps/visualize/group3/_add_to_dashboard.ts +++ b/test/functional/apps/visualize/group3/_add_to_dashboard.ts @@ -158,7 +158,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a new metric to an existing dashboard by value', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -188,7 +188,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a new metric to an existing dashboard by reference', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -220,7 +220,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a existing metric to an existing dashboard by value', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); @@ -265,7 +265,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('adding a existing metric to an existing dashboard by reference', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations(['Visualization AreaChart']); diff --git a/test/functional/apps/visualize/group5/_tsvb_time_series.ts b/test/functional/apps/visualize/group5/_tsvb_time_series.ts index eec30c52018a7..dbddadbe4a746 100644 --- a/test/functional/apps/visualize/group5/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/group5/_tsvb_time_series.ts @@ -195,7 +195,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const cleanup = async () => { const discardDashboardPromptButton = 'discardDashboardPromptButton'; - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); if (await testSubjects.exists(discardDashboardPromptButton)) { await dashboard.clickUnsavedChangesDiscard(discardDashboardPromptButton, true); } diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 36da398b59d1f..6ff48c6b0cfbe 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -51,12 +51,18 @@ export class DashboardPageObject extends FtrService { ? 'test/functional/fixtures/kbn_archiver/ccs/dashboard/legacy/legacy.json' : 'test/functional/fixtures/kbn_archiver/dashboard/legacy/legacy.json'; + public readonly APP_ID = 'dashboards'; + async initTests({ kibanaIndex = this.kibanaIndex, defaultIndex = this.logstashIndex } = {}) { this.log.debug('load kibana index with visualizations and log data'); await this.kibanaServer.savedObjects.cleanStandardList(); await this.kibanaServer.importExport.load(kibanaIndex); await this.kibanaServer.uiSettings.replace({ defaultIndex }); - await this.common.navigateToApp('dashboard'); + await this.navigateToApp(); + } + + public async navigateToApp() { + await this.common.navigateToApp(this.APP_ID); } public async expectAppStateRemovedFromURL() { diff --git a/test/functional/page_objects/time_to_visualize_page.ts b/test/functional/page_objects/time_to_visualize_page.ts index 91864a7995779..280a02354424c 100644 --- a/test/functional/page_objects/time_to_visualize_page.ts +++ b/test/functional/page_objects/time_to_visualize_page.ts @@ -42,7 +42,7 @@ export class TimeToVisualizePageObject extends FtrService { } public async resetNewDashboard() { - await this.common.navigateToApp('dashboard'); + await this.dashboard.navigateToApp(); await this.dashboard.gotoDashboardLandingPage(); await this.dashboard.clickNewDashboard(false); } diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 7cc75446036e9..5deaac7e3a579 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -183,13 +183,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`allows a visualization to be edited`, async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('A Dashboard'); await panelActions.expectExistsEditPanelAction(); }); it(`allows a map to be edited`, async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('dashboard with map'); await panelActions.expectExistsEditPanelAction(); }); diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts index 1ed0e1535a828..7b36b19c32da8 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts @@ -88,7 +88,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('lens by value works without library save permissions', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); @@ -169,13 +169,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const modifiedMarkdownText = 'Modified markdown text'; before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); it('can add a markdown panel by value', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts b/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts index 608d29a6b7abb..baab3bbeb52f7 100644 --- a/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts +++ b/x-pack/test/functional/apps/dashboard/group1/preserve_url.ts @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('goes back to last opened url', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('A Dashboard'); await PageObjects.common.navigateToApp('home'); await appsMenu.clickLink('Dashboard', { category: 'kibana' }); @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('remembers url after switching spaces', async function () { // default space - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('A Dashboard'); await PageObjects.spaceSelector.openSpacesNav(); diff --git a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts index bf76152a459c4..66b31842df00a 100644 --- a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts +++ b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts @@ -128,7 +128,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should launch sample flights data set dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.timePicker.setCommonlyUsedTime('sample_data range'); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts index 2604b942f7313..423c9819a048f 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts @@ -22,7 +22,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts index 5503d1a08dc4e..1d898940a0bf3 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } async function createNewDashboard() { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); } diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts index beb87afce4549..92cc910313615 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts index d36d2b579ae62..f2c8d67d16b7e 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts @@ -23,8 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const queryBar = getService('queryBar'); - const { common, settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ - 'common', + const { settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ 'settings', 'dashboard', 'savedObjects', @@ -57,7 +56,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render all panels on the dashboard', async () => { await dashboardControls.enableControlsLab(); - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.loadSavedDashboard('[8.0.0] Controls Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts index 024c045c4ff87..0dc6f36662952 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[7.12.1] Lens By Value Test Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts index 8485f85dd35a0..c6d947337da21 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('TSVB Index Pattern Smoke Test'); // dashboard should load properly @@ -101,7 +101,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('TSVB 7.13.3'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts index 4824fcb421828..6f8a387d276a0 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts @@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render all panels on the dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[7.12.1] Visualize Test Dashboard'); // dashboard should load properly diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts index 2295c90d60c65..bbf5877f80327 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard(DASHBOARD_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts index 14970ba7764ab..2c0ac33107fea 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard(DASHBOARD_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts index 2692c40af0adf..5d1f590490c4d 100644 --- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should sync colors on dashboard by default', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.dashboard.clickCreateDashboardPrompt(); await dashboardAddPanel.clickCreateNewLink(); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts index e7afd4f9761da..deb5195040800 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_dashboard_drilldown.ts @@ -129,7 +129,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { log.debug('Dashboard Drilldowns:initTests'); await security.testUser.setRoles(['test_logstash_reader', 'global_dashboard_all']); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await elasticChart.setNewChartUiDebugFlag(); @@ -399,7 +399,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Actually use copied dashboards in a new space: - await PageObjects.common.navigateToApp('dashboard', { + await PageObjects.common.navigateToApp('dashboards', { basePath: `/s/${destinationSpaceId}`, }); await PageObjects.dashboard.preserveCrossAppState(); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts index ca057b7421b7c..24e9a249377fa 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/dashboard_to_url_drilldown.ts @@ -22,7 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Dashboard to URL drilldown', function () { before(async () => { log.debug('Dashboard to URL:initTests'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); }); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts index 6e91362e4adfd..dff41ef2ead73 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Explore underlying data - chart action', () => { describe('value click action', () => { it('action exists in chart click popup menu', async () => { - await common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await pieChart.clickOnPieSlice('160,000'); @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let originalTimeRangeDurationHours: number | undefined; it('action exists in chart brush popup menu', async () => { - await common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_AREA_CHART_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts index 8e943c2b3104d..ed504f3711565 100644 --- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts +++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_panel_action.ts @@ -13,7 +13,7 @@ const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; export default function ({ getService, getPageObjects }: FtrProviderContext) { const drilldowns = getService('dashboardDrilldownsManage'); - const { dashboard, discover, common, timePicker } = getPageObjects([ + const { dashboard, discover, timePicker } = getPageObjects([ 'dashboard', 'discover', 'common', @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); before('start on Dashboard landing page', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.preserveCrossAppState(); }); @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after('clean-up custom time range on panel', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); await panelActions.customizePanel(); @@ -75,7 +75,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('carries over panel time range', async () => { - await common.navigateToApp('dashboard'); + await dashboard.navigateToApp(); await dashboard.gotoDashboardEditMode(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME); diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts index c8808e179d70d..c23f991f69f07 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const navigateToDashboardApp = async () => { log.debug('in navigateToDashboardApp'); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await retry.tryForTime(10000, async () => { expect(await PageObjects.dashboard.onDashboardLandingPage()).to.be(true); }); diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts index 4450224d0456c..490ba84c8496c 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts @@ -84,7 +84,7 @@ export default function ({ describe('Print PDF button', () => { it('is available if new', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPdfReportingPanel(); expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); @@ -110,7 +110,7 @@ export default function ({ // Generating and then comparing reports can take longer than the default 60s timeout because the comparePngs // function is taking about 15 seconds per comparison in jenkins. this.timeout(300000); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.checkUsePrintLayout(); @@ -133,7 +133,7 @@ export default function ({ }); it('is available if new', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.reporting.openPngReportingPanel(); expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null); @@ -158,7 +158,7 @@ export default function ({ it('downloads a PDF file with saved search given EuiDataGrid enabled', async function () { await kibanaServer.uiSettings.update({ 'doc_table:legacy': false }); this.timeout(300000); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); @@ -187,7 +187,7 @@ export default function ({ 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce_76.json' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('[K7.6-eCommerce] Revenue Dashboard'); await PageObjects.reporting.openPngReportingPanel(); diff --git a/x-pack/test/functional/apps/discover/async_scripted_fields.js b/x-pack/test/functional/apps/discover/async_scripted_fields.js index d86298405b72d..f5143e5fcc084 100644 --- a/x-pack/test/functional/apps/discover/async_scripted_fields.js +++ b/x-pack/test/functional/apps/discover/async_scripted_fields.js @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.discover.saveSearch('search with warning'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/x-pack/test/functional/apps/discover/saved_searches.ts b/x-pack/test/functional/apps/discover/saved_searches.ts index 8b6f830a11406..85a4d91eabc3c 100644 --- a/x-pack/test/functional/apps/discover/saved_searches.ts +++ b/x-pack/test/functional/apps/discover/saved_searches.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/104578 describe.skip('Customize time range', () => { it('should be possible to customize time range for saved searches on dashboards', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.addSavedSearch('Ecommerce Data'); diff --git a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts index 9213c8459ebe3..91ec034295e2e 100644 --- a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts +++ b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts @@ -41,7 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; const createAndSaveDashboard = async (dashboardName: string) => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); diff --git a/x-pack/test/functional/apps/lens/group4/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts index e94f935323235..50e8c427567d2 100644 --- a/x-pack/test/functional/apps/lens/group4/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/dashboard.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('lens dashboard tests', () => { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await security.testUser.setRoles( [ 'global_dashboard_all', @@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters/timerange by clicking in XYChart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters by right clicking in XYChart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -121,7 +121,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Requires xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled // setting set in kibana.yml to test (not enabled by default) it('should hide old "explore underlying data" action', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); @@ -135,7 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to add filters by clicking in pie chart', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -156,7 +156,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not carry over filters if creating a new lens visualization from within dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultAbsoluteRange(); await filterBar.addFilter({ field: 'geo.src', operation: 'is', value: 'US' }); @@ -174,7 +174,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('CSV export action exists in panel context menu', async () => { const ACTION_ID = 'ACTION_EXPORT_CSV'; const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`; - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -190,7 +190,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show all data from all layers in the inspector', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickCreateNewLink(); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -234,7 +234,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('unlink lens panel from embeddable library', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -270,7 +270,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show validation messages if any error appears', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickCreateNewLink(); @@ -300,7 +300,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should recover lens panel in an error state when fixing search query', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsXYvis'); diff --git a/x-pack/test/functional/apps/lens/group6/error_handling.ts b/x-pack/test/functional/apps/lens/group6/error_handling.ts index 50e1ab439308d..f268e829ca5fb 100644 --- a/x-pack/test/functional/apps/lens/group6/error_handling.ts +++ b/x-pack/test/functional/apps/lens/group6/error_handling.ts @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/lens/missing_fields' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard( 'dashboard containing vis with missing fields' ); @@ -135,7 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'x-pack/test/functional/fixtures/kbn_archiver/lens/fundamental_config_errors_on_dashboard' ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('lens fundamental config errors dash'); const failureElements = await testSubjects.findAll('errorMessageMarkdown'); diff --git a/x-pack/test/functional/apps/lens/group6/lens_reporting.ts b/x-pack/test/functional/apps/lens/group6/lens_reporting.ts index dc241c7f3ac49..3141d2d7651fc 100644 --- a/x-pack/test/functional/apps/lens/group6/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group6/lens_reporting.ts @@ -53,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not cause PDF reports to fail', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await listingTable.clickItemLink('dashboard', 'Lens reportz'); await PageObjects.reporting.openPdfReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts index 42bb90e84a903..c3b279f591cdb 100644 --- a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); }); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js b/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js index 1b4c1914a156b..fcd3d06115508 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/add_to_dashboard.js @@ -83,7 +83,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow new map be added by value to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Very Cool Dashboard'); @@ -113,7 +113,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow existing maps be added by value to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Wonderful Dashboard'); @@ -185,7 +185,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow new map be added by reference to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Super Cool Dashboard'); @@ -215,7 +215,7 @@ export default function ({ getPageObjects, getService }) { }); it('should allow existing maps be added by reference to an existing dashboard', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('My Amazing Dashboard'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js index 69c12ed786d23..2750bf3a7f68d 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/dashboard.js @@ -35,7 +35,7 @@ export default function ({ getPageObjects, getService }) { defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: true, }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.waitForRenderComplete(); }); @@ -164,7 +164,7 @@ export default function ({ getPageObjects, getService }) { // see https://github.com/elastic/kibana/issues/61596 on why it is specific to maps it("dashboard's back button should navigate to previous page", async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js index aa54a54196f95..45b1754722153 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_library.js @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }) { await kibanaServer.uiSettings.replace({ defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickEditorMenuButton(); await PageObjects.visualize.clickMapsApp(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js index ba0ac1153aa53..036edc77df796 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/embeddable_state.js @@ -21,7 +21,7 @@ export default function ({ getPageObjects, getService }) { await kibanaServer.uiSettings.replace({ defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addEmbeddable('document example', 'map'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js b/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js index b08c284506e18..4da0d2af33894 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/filter_by_map_extent.js @@ -19,7 +19,7 @@ export default function ({ getPageObjects, getService }) { ['test_logstash_reader', 'global_maps_all', 'global_dashboard_all'], { skipBrowserRefresh: true } ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode('filter by map extent dashboard'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js b/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js index faa65d6ab183a..14ff01e4da46f 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/save_and_return.js @@ -41,7 +41,7 @@ export default function ({ getPageObjects, getService }) { describe('new map', () => { beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.clickEditorMenuButton(); await dashboardAddPanel.clickVisType('maps'); @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }) { describe('edit existing map', () => { beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.dashboard.switchToEditMode(); await dashboardPanelActions.editPanelByTitle('join example'); diff --git a/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js index c3c014447a36d..e7265f4d7883d 100644 --- a/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/group2/embeddable/tooltip_filter_actions.js @@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }) { defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a', }); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('dash for tooltip filter action test'); diff --git a/x-pack/test/functional/apps/maps/group3/reports/index.ts b/x-pack/test/functional/apps/maps/group3/reports/index.ts index cb4f64b348ba5..0249658b70055 100644 --- a/x-pack/test/functional/apps/maps/group3/reports/index.ts +++ b/x-pack/test/functional/apps/maps/group3/reports/index.ts @@ -61,7 +61,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('PNG file matches the baseline image, using sample geo data', async function () { await reporting.initEcommerce(); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('Ecommerce Map'); await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); @@ -73,7 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('PNG file matches the baseline image, using embeddable example', async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); await PageObjects.reporting.openPngReportingPanel(); await PageObjects.reporting.clickGenerateReportButton(); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts index f2273b168489b..9927f37a6c8f9 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_charts_dashboard_embeddables.ts @@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { testData.jobConfig, testData.datafeedConfig ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts index 7058286f3d5b3..2760c3c136557 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/anomaly_embeddables_migration.ts @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { testDataList.map((d) => d.dashboardSavedObject) ); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const { dashboardSavedObject, panelTitle, type } = testData; describe(`for ${panelTitle}`, function () { before(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); after(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts index 90884b1644774..eef7461bec609 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml.ts @@ -55,7 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); afterEach(async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts index b8e39eaa3ba2c..089141ba663e7 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/lens_to_ml_with_wizard.ts @@ -107,7 +107,7 @@ export default function ({ getService, getPageObject, getPageObjects }: FtrProvi }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); let tabsCount = 1; diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts index 63cac23c5ed1d..3ce45876f77b1 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_integrations/map_to_ml.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); beforeEach(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); }); afterEach(async () => { diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts index d4814a6f0ec11..47c6e7725686d 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_dashboard.ts @@ -58,7 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it(`displays Field statistics table in Dashboard when enabled`, async function () { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await dashboardAddPanel.addSavedSearch(savedSearchTitle); @@ -96,7 +96,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it(`doesn't display Field statistics table in Dashboard when disabled`, async function () { await ml.testResources.setAdvancedSettingProperty(SHOW_FIELD_STATISTICS, false); - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.gotoDashboardEditMode(dashboardTitle); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 7761af2684cbb..b6f1e94e83af3 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -59,7 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(newObjectCount - initialObjectCount).to.eql(82); // logstash by reference dashboard with drilldowns - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('by_reference_drilldown'); // dashboard should load properly await PageObjects.dashboard.expectOnDashboard('by_reference_drilldown'); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index 56f6e7b987873..113282e5a80d6 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -171,7 +171,7 @@ export default function spaceSelectorFunctionalTests({ describe('displays separate data for each space', () => { it('in the default space', async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await expectDashboardRenders('[Logs] Web Traffic'); }); diff --git a/x-pack/test/functional/apps/visualize/telemetry.ts b/x-pack/test/functional/apps/visualize/telemetry.ts index 6bf1ae510a5f4..773a21d120c93 100644 --- a/x-pack/test/functional/apps/visualize/telemetry.ts +++ b/x-pack/test/functional/apps/visualize/telemetry.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await retry.try(async () => { - await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.loadSavedDashboard('visualizations'); await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.dashboard.waitForRenderComplete(); From a6c25b15aa1897dc93337c54350aeb544cd921d6 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Mon, 18 Sep 2023 18:27:16 +0200 Subject: [PATCH 21/58] [kbn] Subscription tracking (cont.) (#157392) ## Summary (this is the continuation of https://github.com/elastic/kibana/pull/143910, which I started before my parental leave and which is impossible to rebase) With the introduction of more features that are part of licenses, we're also adding more upsells to Kibana. These upsells advertise the feature, they explain which license is required in order to use said feature and they will link the client to the subscription page. Take the upsell for more insights in the alert flyout as an example: Screenshot 2022-10-18 at 16 39 52 Upsells come in all different shapes. Somtimes they're just links, sometimes full pages and sometimes interactive popups. They are also used across all solutions in Kibana. There is currently no specific tracking in place for these types of elements yet. Meaning we don't know how many people interact with them, how many custerms see them and how well they perform in terms of conversions. It is technically already possible to analyze clicks on these elements as part of the regular Kibana click tracking but it would require setting up queries with lots of `data-test-subj` and `url` filters for the right click events. Even if we wanted to set up tracking dashboards with that data, we would still not know how often upsells are seen which is necessary to calculate their click-through-rate. That rate can give an indicator if an upsell performs well or if we might want to improve it in the future. For that reason, I'm proposing a dedicated set of tracking methods to capture `impressions` and `clicks` for upsells. No conversion tracking as of yet, but I will get back to that later. This PR introduces the `@kbn/subscription-tracking` package. It leverages the `@kbn/analytics-client` package to send dedicated subscription tracking events. It comes with a set of React components that automatically track impressions and click events. Consumers of those components only need to specify a `subscription context` that gives more details on the type of feature that is advertised and the location of the upsell. ```typescript import { SubscriptionLink } from '@kbn/subscription-tracking'; import type { SubscriptionContextData } from '@kbn/subscription-tracking'; const subscriptionContext: SubscriptionContextData = { feature: 'threat-intelligence', source: 'security__threat-intelligence', }; export const Paywall = () => { return (
Upgrade to Platinum to get this feature
) } ``` The example above uses a `SubscriptionLink` which is a wrapper of `EuiLink` . So it behaves just like a normal link. Alternatively, upsells can also use a `SubscriptionButton` or `SubscriptionButtonEmpty` which wrap `EuiButton` and `EuiButtonEmpty` respectively. When the link is mounted, it will send off an `impression` event with the given `subscriptionContext`. That piece of metadata consists of an identifier of the advertised feature (in this case `threat-intelligence`) and the `source` of the impression (in this case the `threat-intelligence` page in the `security` solution). `source` follows the following format: `{solution-identifier}__location-identifier`. There are no special rules for how to name these identifiers but it's good practise to make sure that `feature` has the same value for all upsells advertising the same feature (e.g. use enums for features to prevent spelling mistakes). Upon interaction with the upsell link/button, a special `click` event is sent, which, again, contains the same subscription context. If you want to use the `subscription-tracking` elements in your solution, you have to set up a `SubscriptionTrackingProvider` in your plugin setup and register the tracking events on startup. This PR contains an example for this setup for the security plugin and some of its sub-plugins. ## Next steps - There are currently no dedicated tracking dashboards for these events which I am planning to set up in the future. - Since I only had a week's worth of time, I did not manage to add conversion tracking. The addition of those events might be a lot harder as well since the current license flow does not integrate seamlessly into Kibana - All upsells currently link to the license management page which currently does not inform customers about our license and cloud offering. It seems to me like a weak link in the subscription funnel and it would be great to improve on that page. - potential improvement: Send `impression` event when the element becomes visible in the viewport instead of when the element is mounted ### Checklist - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + .../tsconfig.json | 13 +-- packages/kbn-subscription-tracking/README.md | 35 ++++++ packages/kbn-subscription-tracking/index.ts | 17 +++ .../kbn-subscription-tracking/jest.config.js | 13 +++ .../kbn-subscription-tracking/kibana.jsonc | 5 + packages/kbn-subscription-tracking/mocks.tsx | 28 +++++ .../kbn-subscription-tracking/package.json | 6 + .../src/helpers.test.ts | 45 ++++++++ .../kbn-subscription-tracking/src/helpers.ts | 14 +++ .../src/services.tsx | 70 ++++++++++++ .../src/subscription_elements.test.tsx | 108 ++++++++++++++++++ .../src/subscription_elements.tsx | 79 +++++++++++++ .../src/use_go_to_subscription.ts | 35 ++++++ .../src/use_impression.ts | 57 +++++++++ .../kbn-subscription-tracking/tsconfig.json | 10 ++ packages/kbn-subscription-tracking/types.ts | 47 ++++++++ tsconfig.base.json | 2 + .../components/subscription_not_allowed.tsx | 15 ++- .../cloud_security_posture/public/plugin.tsx | 16 ++- .../public/test/test_provider.tsx | 10 +- .../cloud_security_posture/tsconfig.json | 9 +- .../plugins/licensing/public/plugin.test.ts | 65 +++++++++++ x-pack/plugins/licensing/public/plugin.ts | 2 + x-pack/plugins/licensing/tsconfig.json | 14 +-- .../security_solution/public/app/index.tsx | 9 +- .../security_solution/public/app/types.ts | 2 + .../insights/related_alerts_upsell.tsx | 29 ++--- .../public/common/mock/test_providers.tsx | 83 +++++++------- .../security_solution/public/plugin.tsx | 6 + .../plugins/security_solution/tsconfig.json | 3 +- .../public/components/paywall.stories.tsx | 2 +- .../public/components/paywall.tsx | 27 ++--- .../public/containers/enterprise_guard.tsx | 9 +- .../public/mocks/story_providers.tsx | 5 +- .../public/mocks/test_providers.tsx | 13 ++- .../threat_intelligence/public/plugin.tsx | 16 ++- .../plugins/threat_intelligence/tsconfig.json | 9 +- yarn.lock | 4 + 40 files changed, 801 insertions(+), 133 deletions(-) create mode 100644 packages/kbn-subscription-tracking/README.md create mode 100644 packages/kbn-subscription-tracking/index.ts create mode 100644 packages/kbn-subscription-tracking/jest.config.js create mode 100644 packages/kbn-subscription-tracking/kibana.jsonc create mode 100644 packages/kbn-subscription-tracking/mocks.tsx create mode 100644 packages/kbn-subscription-tracking/package.json create mode 100644 packages/kbn-subscription-tracking/src/helpers.test.ts create mode 100644 packages/kbn-subscription-tracking/src/helpers.ts create mode 100644 packages/kbn-subscription-tracking/src/services.tsx create mode 100644 packages/kbn-subscription-tracking/src/subscription_elements.test.tsx create mode 100644 packages/kbn-subscription-tracking/src/subscription_elements.tsx create mode 100644 packages/kbn-subscription-tracking/src/use_go_to_subscription.ts create mode 100644 packages/kbn-subscription-tracking/src/use_impression.ts create mode 100644 packages/kbn-subscription-tracking/tsconfig.json create mode 100644 packages/kbn-subscription-tracking/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 62c41894801c1..539735e934dc7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -723,6 +723,7 @@ test/server_integration/plugins/status_plugin_b @elastic/kibana-core packages/kbn-std @elastic/kibana-core packages/kbn-stdio-dev-helpers @elastic/kibana-operations packages/kbn-storybook @elastic/kibana-operations +packages/kbn-subscription-tracking @elastic/security-threat-hunting-investigations x-pack/plugins/synthetics @elastic/uptime x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture @elastic/response-ops x-pack/test/plugin_api_perf/plugins/task_manager_performance @elastic/response-ops diff --git a/package.json b/package.json index d3f6a91735108..6ebe1726de727 100644 --- a/package.json +++ b/package.json @@ -722,6 +722,7 @@ "@kbn/status-plugin-a-plugin": "link:test/server_integration/plugins/status_plugin_a", "@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b", "@kbn/std": "link:packages/kbn-std", + "@kbn/subscription-tracking": "link:packages/kbn-subscription-tracking", "@kbn/synthetics-plugin": "link:x-pack/plugins/synthetics", "@kbn/task-manager-fixture-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture", "@kbn/task-manager-performance-plugin": "link:x-pack/test/plugin_api_perf/plugins/task_manager_performance", diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index 4c2daa18d079d..c6efe4287effc 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -2,14 +2,9 @@ "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "types": [ - "jest", - "node" - ] + "types": ["jest", "node"] }, - "include": [ - "**/*.ts" - ], + "include": ["**/*.ts"], "kbn_references": [ "@kbn/logging", "@kbn/analytics-client", @@ -20,7 +15,5 @@ "@kbn/core-base-browser-mocks", "@kbn/core-injected-metadata-browser-mocks" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/packages/kbn-subscription-tracking/README.md b/packages/kbn-subscription-tracking/README.md new file mode 100644 index 0000000000000..4f84593980881 --- /dev/null +++ b/packages/kbn-subscription-tracking/README.md @@ -0,0 +1,35 @@ +# @kbn/subscription-tracking + +This package leverages the `@kbn/analytics-client` package to send dedicated subscription tracking events. + +It provides a set of React components that automatically track `impression` and `click` events. Consumers of those components need to specify a `subscription context` that gives more details on the type of feature that is advertised and the location of the upsell. + +```typescript +import { SubscriptionLink } from '@kbn/subscription-tracking'; +import type { SubscriptionContext } from '@kbn/subscription-tracking'; + +const subscriptionContext: SubscriptionContext = { + feature: 'threat-intelligence', + source: 'security__threat-intelligence', +}; + +export const Paywall = () => { + return ( +
+ + Upgrade to Platinum to get this feature + +
+ ); +}; +``` + +The example above uses a `SubscriptionLink` which is a wrapper of `EuiLink` . So it behaves just like a normal link. Alternatively, upsells can also use a `SubscriptionButton` or `SubscriptionButtonEmpty` which wrap `EuiButton` and `EuiButtonEmpty` respectively. + +When the link is mounted, it will send off an `impression` event with the given `subscriptionContext`. That piece of metadata consists of an identifier of the advertised feature (in this case `threat-intelligence`) and the `source` (aka location) of the impression (in this case the `threat-intelligence` page in the `security` solution). `source` follows the following format: `{solution-identifier}__location-identifier`. + +There are no special rules for how to name these identifiers but it's good practise to make sure that `feature` has the same value for all upsells advertising the same feature (e.g. use enums for features to prevent spelling mistakes). + +Upon interaction with the upsell link/button, a special `click` event is sent, which, again, contains the same subscription context. + +If you want to use the `subscription-tracking` elements in your app, you have to set up a `SubscriptionTrackingProvider` in your plugin setup and register the tracking events on startup. Have a look at https://github.com/elastic/kibana/pull/143910 for an example of an integration. diff --git a/packages/kbn-subscription-tracking/index.ts b/packages/kbn-subscription-tracking/index.ts new file mode 100644 index 0000000000000..de17c595918d5 --- /dev/null +++ b/packages/kbn-subscription-tracking/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/subscription_elements'; + +export { + SubscriptionTrackingContext, + SubscriptionTrackingProvider, + registerEvents, +} from './src/services'; + +export * from './types'; diff --git a/packages/kbn-subscription-tracking/jest.config.js b/packages/kbn-subscription-tracking/jest.config.js new file mode 100644 index 0000000000000..edc1839850dae --- /dev/null +++ b/packages/kbn-subscription-tracking/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-subscription-tracking'], +}; diff --git a/packages/kbn-subscription-tracking/kibana.jsonc b/packages/kbn-subscription-tracking/kibana.jsonc new file mode 100644 index 0000000000000..e165baebfa76b --- /dev/null +++ b/packages/kbn-subscription-tracking/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/subscription-tracking", + "owner": "@elastic/security-threat-hunting-investigations" +} diff --git a/packages/kbn-subscription-tracking/mocks.tsx b/packages/kbn-subscription-tracking/mocks.tsx new file mode 100644 index 0000000000000..b918f9bba2828 --- /dev/null +++ b/packages/kbn-subscription-tracking/mocks.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { analyticsClientMock } from '@kbn/analytics-client/src/mocks'; + +import { SubscriptionTrackingProvider } from './src/services'; + +const analyticsClientMockInst = analyticsClientMock.create(); + +/** + * Mock for the external services provider. Only use in tests! + */ +export const MockSubscriptionTrackingProvider: FC = ({ children }) => { + return ( + + {children} + + ); +}; diff --git a/packages/kbn-subscription-tracking/package.json b/packages/kbn-subscription-tracking/package.json new file mode 100644 index 0000000000000..e9dd11b56c81b --- /dev/null +++ b/packages/kbn-subscription-tracking/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/subscription-tracking", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-subscription-tracking/src/helpers.test.ts b/packages/kbn-subscription-tracking/src/helpers.test.ts new file mode 100644 index 0000000000000..fa000567d35d3 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/helpers.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { isValidContext } from './helpers'; + +describe('tracking', () => { + describe('isValidLocation', () => { + it('identifies correct contexts', () => { + expect( + isValidContext({ + feature: 'test', + source: 'security__test', + }) + ).toBeTruthy(); + }); + + it('identifies incorrect contexts', () => { + expect( + isValidContext({ + feature: '', + source: 'security__', + }) + ).toBeFalsy(); + + expect( + isValidContext({ + feature: 'test', + source: 'security__', + }) + ).toBeFalsy(); + + expect( + isValidContext({ + feature: '', + source: 'security__test', + }) + ).toBeFalsy(); + }); + }); +}); diff --git a/packages/kbn-subscription-tracking/src/helpers.ts b/packages/kbn-subscription-tracking/src/helpers.ts new file mode 100644 index 0000000000000..251c0d1c04116 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/helpers.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SubscriptionContextData } from '../types'; + +const sourceStringRegEx = /^(\w[\w\-_]*)__(\w[\w\-_]*)$/; +export function isValidContext(context: SubscriptionContextData): boolean { + return context.feature.length > 0 && sourceStringRegEx.test(context.source); +} diff --git a/packages/kbn-subscription-tracking/src/services.tsx b/packages/kbn-subscription-tracking/src/services.tsx new file mode 100644 index 0000000000000..857bd0b0dcd89 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/services.tsx @@ -0,0 +1,70 @@ +/* + * 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, { FC, useContext } from 'react'; +import type { AnalyticsClient, EventTypeOpts } from '@kbn/analytics-client'; +import { EVENT_NAMES, Services, SubscriptionContextData } from '../types'; + +export const SubscriptionTrackingContext = React.createContext(null); + +/** + * External services provider + */ +export const SubscriptionTrackingProvider: FC = ({ children, ...services }) => { + return ( + + {children} + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useServices() { + const context = useContext(SubscriptionTrackingContext); + + if (!context) { + throw new Error( + 'SubscriptionTrackingContext is missing. Ensure your component or React root is wrapped with SubscriptionTrackingProvider.' + ); + } + + return context; +} + +const subscriptionContextSchema: EventTypeOpts['schema'] = { + source: { + type: 'keyword', + _meta: { + description: + 'A human-readable identifier describing the location of the beginning of the subscription flow', + }, + }, + feature: { + type: 'keyword', + _meta: { + description: 'A human-readable identifier describing the feature that is being promoted', + }, + }, +}; + +/** + * Registers the subscription-specific event types + */ +export function registerEvents(analyticsClient: Pick) { + analyticsClient.registerEventType({ + eventType: EVENT_NAMES.IMPRESSION, + schema: subscriptionContextSchema, + }); + + analyticsClient.registerEventType({ + eventType: EVENT_NAMES.CLICK, + schema: subscriptionContextSchema, + }); +} diff --git a/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx b/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx new file mode 100644 index 0000000000000..1795bbf42dd0c --- /dev/null +++ b/packages/kbn-subscription-tracking/src/subscription_elements.test.tsx @@ -0,0 +1,108 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import { + SubscriptionLink, + SubscriptionButton, + SubscriptionButtonEmpty, +} from './subscription_elements'; +import { SubscriptionTrackingProvider } from './services'; +import { EVENT_NAMES, Services, SubscriptionContextData } from '../types'; +import { coolDownTimeMs, resetCoolDown } from './use_impression'; + +const testServices: Services = { + navigateToApp: jest.fn(), + analyticsClient: { + reportEvent: jest.fn(), + registerEventType: jest.fn(), + } as any, +}; +const testContext: SubscriptionContextData = { feature: 'test', source: 'security__test' }; + +const WithProviders: React.FC = ({ children }) => ( + + {children} + +); + +const renderWithProviders = (children: React.ReactElement) => + render(children, { wrapper: WithProviders }); + +const reset = () => { + jest.resetAllMocks(); + resetCoolDown(); +}; + +describe('SubscriptionElements', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + [SubscriptionButton, SubscriptionLink, SubscriptionButtonEmpty].forEach((SubscriptionElement) => { + describe(SubscriptionElement.name, () => { + beforeEach(reset); + + it('renders the children correctly', () => { + renderWithProviders( + Hello + ); + expect(screen.getByText('Hello')).toBeTruthy(); + }); + + it('fires an impression event when rendered', () => { + renderWithProviders(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith( + EVENT_NAMES.IMPRESSION, + testContext + ); + }); + + it('fires an impression event when rendered (but only once)', () => { + const { unmount } = renderWithProviders( + + ); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1); + unmount(); + + // does not create an impression again when remounted + const { unmount: unmountAgain } = renderWithProviders( + + ); + unmountAgain(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1); + + // only creates anew impression when the cooldown time has passed + jest.setSystemTime(Date.now() + coolDownTimeMs); + renderWithProviders(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(2); + }); + + it('tracks a click when clicked and navigates to page', () => { + renderWithProviders( + hello + ); + + screen.getByText('hello').click(); + expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith( + EVENT_NAMES.CLICK, + testContext + ); + expect(testServices.navigateToApp).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/packages/kbn-subscription-tracking/src/subscription_elements.tsx b/packages/kbn-subscription-tracking/src/subscription_elements.tsx new file mode 100644 index 0000000000000..f29c58d8a0a41 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/subscription_elements.tsx @@ -0,0 +1,79 @@ +/* + * 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 { EuiLink, EuiButton, EuiButtonEmpty } from '@elastic/eui'; +import type { EuiLinkProps, EuiButtonEmptyProps, EuiButtonProps } from '@elastic/eui'; +import { useGoToSubscription } from './use_go_to_subscription'; +import { useImpression } from './use_impression'; +import type { SubscriptionContextData } from '../types'; + +interface CommonProps { + /** The context information for this subscription element */ + subscriptionContext: SubscriptionContextData; +} + +export type SubscriptionLinkProps = EuiLinkProps & CommonProps; + +/** + * Wrapper around `EuiLink` that provides subscription events + */ +export function SubscriptionLink({ + subscriptionContext, + children, + ...restProps +}: SubscriptionLinkProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} + +export type SubscriptionButtonProps = EuiButtonProps & CommonProps; + +/** + * Wrapper around `EuiButton` that provides subscription events + */ +export function SubscriptionButton({ + subscriptionContext, + children, + ...restProps +}: SubscriptionButtonProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} + +export type SubscriptionButtonEmptyProps = EuiButtonEmptyProps & CommonProps; + +/** + * Wrapper around `EuiButtonEmpty` that provides subscription events + */ +export function SubscriptionButtonEmpty({ + subscriptionContext, + children, + ...restProps +}: SubscriptionButtonEmptyProps) { + const goToSubscription = useGoToSubscription({ subscriptionContext }); + useImpression(subscriptionContext); + + return ( + + {children} + + ); +} diff --git a/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts b/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts new file mode 100644 index 0000000000000..6c93fb27ee9bd --- /dev/null +++ b/packages/kbn-subscription-tracking/src/use_go_to_subscription.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { useCallback } from 'react'; +import { isValidContext } from './helpers'; +import { useServices } from './services'; +import { EVENT_NAMES, SubscriptionContextData } from '../types'; + +interface Options { + subscriptionContext: SubscriptionContextData; +} + +/** + * Provides a navigation function that navigates to the subscription + * management page. When the function executes, a click event with the + * given context is emitted. + */ +export const useGoToSubscription = ({ subscriptionContext }: Options) => { + const { navigateToApp, analyticsClient } = useServices(); + const goToSubscription = useCallback(() => { + if (isValidContext(subscriptionContext)) { + analyticsClient.reportEvent(EVENT_NAMES.CLICK, subscriptionContext); + } else { + // eslint-disable-next-line no-console + console.error('The provided subscription context is invalid', subscriptionContext); + } + navigateToApp('management', { path: 'stack/license_management' }); + }, [analyticsClient, navigateToApp, subscriptionContext]); + + return goToSubscription; +}; diff --git a/packages/kbn-subscription-tracking/src/use_impression.ts b/packages/kbn-subscription-tracking/src/use_impression.ts new file mode 100644 index 0000000000000..eb8aa4c2e0ec5 --- /dev/null +++ b/packages/kbn-subscription-tracking/src/use_impression.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useEffect } from 'react'; +import { isValidContext } from './helpers'; +import { useServices } from './services'; +import { EVENT_NAMES, SubscriptionContextData } from '../types'; + +/** + * Sends an impression event with the given context. + * + * Note: impression events are throttled and will not fire more + * often than once every 30 seconds. + */ +export const useImpression = (context: SubscriptionContextData) => { + const { analyticsClient } = useServices(); + + useEffect(() => { + if (!isValidContext(context)) { + // eslint-disable-next-line no-console + console.error('The provided subscription context is invalid', context); + return; + } + if (!isCoolingDown(context)) { + analyticsClient.reportEvent(EVENT_NAMES.IMPRESSION, context); + coolDown(context); + } + }, [analyticsClient, context]); +}; + +/** + * Impressions from the same context should not fire more than once every 30 seconds. + * This prevents logging too many impressions in case a page is reloaded often or + * if the user is navigating back and forth rapidly. + */ +export const coolDownTimeMs = 30 * 1000; +let impressionCooldown = new WeakMap(); + +function isCoolingDown(context: SubscriptionContextData) { + const previousLog = impressionCooldown.get(context); + + // we logged before and we are in the cooldown period + return previousLog && Date.now() - previousLog < coolDownTimeMs; +} + +function coolDown(context: SubscriptionContextData) { + impressionCooldown.set(context, Date.now()); +} + +export function resetCoolDown() { + impressionCooldown = new WeakMap(); +} diff --git a/packages/kbn-subscription-tracking/tsconfig.json b/packages/kbn-subscription-tracking/tsconfig.json new file mode 100644 index 0000000000000..677e9db998bb7 --- /dev/null +++ b/packages/kbn-subscription-tracking/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node", "react"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["target/**/*"], + "kbn_references": ["@kbn/analytics-client"] +} diff --git a/packages/kbn-subscription-tracking/types.ts b/packages/kbn-subscription-tracking/types.ts new file mode 100644 index 0000000000000..a2adf0c6d90c5 --- /dev/null +++ b/packages/kbn-subscription-tracking/types.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { AnalyticsClient } from '@kbn/analytics-client'; + +enum SolutionIdentifier { + observability = 'observability', + security = 'security', +} +type LocationString = string; +type SourceIdentifier = `${SolutionIdentifier}__${LocationString}`; +/** + * A piece of metadata which consists of an identifier of the advertised feature and + * the `source` (e.g. location) of the subscription element. + */ +export interface SubscriptionContextData { + /** + * A human-readable identifier describing the location of the beginning of the + * subscription flow. + * Location identifiers are prefixed with a solution identifier, e.g. `security__` + * + * @example "security__host-overview" - the user is looking at an upsell button + * on the host overview page in the security solution + */ + source: SourceIdentifier; + + /** + * A human-readable identifier describing the feature that is being promoted. + * + * @example "alerts-by-process-ancestry" + */ + feature: string; +} + +export interface Services { + navigateToApp: (app: string, options: { path: string }) => void; + analyticsClient: Pick; +} + +export enum EVENT_NAMES { + CLICK = 'subscription__upsell__click', + IMPRESSION = 'subscription__upsell__impression', +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 178df4e7c449f..04071f12af018 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1440,6 +1440,8 @@ "@kbn/stdio-dev-helpers/*": ["packages/kbn-stdio-dev-helpers/*"], "@kbn/storybook": ["packages/kbn-storybook"], "@kbn/storybook/*": ["packages/kbn-storybook/*"], + "@kbn/subscription-tracking": ["packages/kbn-subscription-tracking"], + "@kbn/subscription-tracking/*": ["packages/kbn-subscription-tracking/*"], "@kbn/synthetics-plugin": ["x-pack/plugins/synthetics"], "@kbn/synthetics-plugin/*": ["x-pack/plugins/synthetics/*"], "@kbn/task-manager-fixture-plugin": ["x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture"], diff --git a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx index 282ea71125655..644455e5a2a68 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx @@ -5,9 +5,16 @@ * 2.0. */ -import { EuiEmptyPrompt, EuiPageSection, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; +import { EuiEmptyPrompt, EuiPageSection } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { SubscriptionLink } from '@kbn/subscription-tracking'; +import type { SubscriptionContextData } from '@kbn/subscription-tracking'; + +const subscriptionContext: SubscriptionContextData = { + feature: 'cloud-security-posture', + source: 'security__cloud-security-posture', +}; export const SubscriptionNotAllowed = ({ licenseManagementLocator, @@ -34,12 +41,12 @@ export const SubscriptionNotAllowed = ({ defaultMessage="To use these cloud security features, you must {link}." values={{ link: ( - + - + ), }} /> diff --git a/x-pack/plugins/cloud_security_posture/public/plugin.tsx b/x-pack/plugins/cloud_security_posture/public/plugin.tsx index a941f1f770b13..32e5ee577e40e 100755 --- a/x-pack/plugins/cloud_security_posture/public/plugin.tsx +++ b/x-pack/plugins/cloud_security_posture/public/plugin.tsx @@ -8,6 +8,7 @@ import React, { lazy, Suspense } from 'react'; import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking'; import { CspLoadingState } from './components/csp_loading_state'; import type { CspRouterProps } from './application/csp_router'; import type { @@ -71,11 +72,16 @@ export class CspPlugin const App = (props: CspRouterProps) => ( -
- - - -
+ +
+ + + +
+
); diff --git a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx index b8a5a0e5c4ae7..57fc2935e5708 100755 --- a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx +++ b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx @@ -11,7 +11,7 @@ import { I18nProvider } from '@kbn/i18n-react'; // eslint-disable-next-line no-restricted-imports import { Router } from 'react-router-dom'; import { Route, Routes } from '@kbn/shared-ux-router'; - +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -48,9 +48,11 @@ export const TestProvider: React.FC> = ({ - - <>{children}} /> - + + + <>{children}} /> + + diff --git a/x-pack/plugins/cloud_security_posture/tsconfig.json b/x-pack/plugins/cloud_security_posture/tsconfig.json index 07c86f24ea18a..307394e41d84b 100755 --- a/x-pack/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/plugins/cloud_security_posture/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", + "outDir": "target/types" }, "include": [ "common/**/*", @@ -49,9 +49,8 @@ "@kbn/core-saved-objects-server", "@kbn/share-plugin", "@kbn/core-http-server", - "@kbn/core-http-browser" + "@kbn/core-http-browser", + "@kbn/subscription-tracking" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 5c571d0c6a40b..9804f1ba8e1b7 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -16,6 +16,7 @@ import { License } from '../common/license'; import { licenseMock } from '../common/licensing.mock'; import { coreMock } from '@kbn/core/public/mocks'; import { HttpInterceptor } from '@kbn/core/public'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; const coreStart = coreMock.createStart(); describe('licensing plugin', () => { @@ -442,5 +443,69 @@ describe('licensing plugin', () => { expect(removeInterceptorMock).toHaveBeenCalledTimes(1); }); + + it('registers the subscription upsell events', async () => { + const sessionStorage = coreMock.createStorage(); + plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage); + + const coreSetup = coreMock.createSetup(); + + await plugin.setup(coreSetup); + await plugin.stop(); + + expect(findRegisteredEventTypeByName('subscription__upsell__click', coreSetup.analytics)) + .toMatchInlineSnapshot(` + Array [ + Object { + "eventType": "subscription__upsell__click", + "schema": Object { + "feature": Object { + "_meta": Object { + "description": "A human-readable identifier describing the feature that is being promoted", + }, + "type": "keyword", + }, + "source": Object { + "_meta": Object { + "description": "A human-readable identifier describing the location of the beginning of the subscription flow", + }, + "type": "keyword", + }, + }, + }, + ] + `); + expect(findRegisteredEventTypeByName('subscription__upsell__impression', coreSetup.analytics)) + .toMatchInlineSnapshot(` + Array [ + Object { + "eventType": "subscription__upsell__impression", + "schema": Object { + "feature": Object { + "_meta": Object { + "description": "A human-readable identifier describing the feature that is being promoted", + }, + "type": "keyword", + }, + "source": Object { + "_meta": Object { + "description": "A human-readable identifier describing the location of the beginning of the subscription flow", + }, + "type": "keyword", + }, + }, + }, + ] + `); + }); }); }); + +function findRegisteredEventTypeByName( + eventTypeName: string, + analyticsClientMock: jest.Mocked +) { + return analyticsClientMock.registerEventType.mock.calls.find( + ([{ eventType }]) => eventType === eventTypeName + )!; +} diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index 3953a29a08214..bc350675d7dd2 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -8,6 +8,7 @@ import { Observable, Subject, Subscription } from 'rxjs'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { registerEvents as registerSubscriptionTrackingEvents } from '@kbn/subscription-tracking'; import { ILicense } from '../common/types'; import { LicensingPluginSetup, LicensingPluginStart } from './types'; import { createLicenseUpdate } from '../common/license_update'; @@ -84,6 +85,7 @@ export class LicensingPlugin implements Plugin { if (license.isAvailable) { diff --git a/x-pack/plugins/licensing/tsconfig.json b/x-pack/plugins/licensing/tsconfig.json index 0abd1517b2d3a..323f77b3b0ebc 100644 --- a/x-pack/plugins/licensing/tsconfig.json +++ b/x-pack/plugins/licensing/tsconfig.json @@ -2,13 +2,9 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", - "isolatedModules": true, + "isolatedModules": true }, - "include": [ - "public/**/*", - "server/**/*", - "common/**/*" - ], + "include": ["public/**/*", "server/**/*", "common/**/*"], "kbn_references": [ "@kbn/core", "@kbn/kibana-react-plugin", @@ -18,8 +14,8 @@ "@kbn/std", "@kbn/i18n", "@kbn/analytics-client", + "@kbn/subscription-tracking", + "@kbn/core-analytics-browser" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 91eca678f6d3c..e635c2d9fd3d3 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking'; import { SecurityApp } from './app'; import type { RenderAppProps } from './types'; import { AppRoutes } from './app_routes'; @@ -21,6 +22,7 @@ export const renderApp = ({ usageCollection, subPluginRoutes, theme$, + subscriptionTrackingServices, }: RenderAppProps): (() => void) => { const ApplicationUsageTrackingProvider = usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; @@ -34,7 +36,12 @@ export const renderApp = ({ theme$={theme$} > - + + + , element diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 578a4800f7f64..66bab19c945fe 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -19,6 +19,7 @@ import type { RouteProps } from 'react-router-dom'; import type { AppMountParameters } from '@kbn/core/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { TableState } from '@kbn/securitysolution-data-table'; +import type { Services as SubscriptionTrackingServices } from '@kbn/subscription-tracking'; import type { ExploreReducer, ExploreState } from '../explore'; import type { StartServices } from '../types'; @@ -29,6 +30,7 @@ export interface RenderAppProps extends AppMountParameters { services: StartServices; store: Store; subPluginRoutes: RouteProps[]; + subscriptionTrackingServices: SubscriptionTrackingServices; usageCollection?: UsageCollectionSetup; } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx index ba225495a032b..303e55ff66b97 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_upsell.tsx @@ -5,12 +5,18 @@ * 2.0. */ -import React, { useCallback } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiIcon, EuiText } from '@elastic/eui'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { SubscriptionLink } from '@kbn/subscription-tracking'; +import type { SubscriptionContextData } from '@kbn/subscription-tracking'; import { INSIGHTS_UPSELL } from './translations'; -import { useNavigation } from '../../../lib/kibana'; + +const subscriptionContext: SubscriptionContextData = { + feature: 'alert-details-insights', + source: 'security__alert-details-flyout', +}; const UpsellContainer = euiStyled.div` border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; @@ -23,15 +29,6 @@ const StyledIcon = euiStyled(EuiIcon)` `; export const RelatedAlertsUpsell = React.memo(() => { - const { getAppUrl, navigateTo } = useNavigation(); - const subscriptionUrl = getAppUrl({ - appId: 'management', - path: 'stack/license_management', - }); - const goToSubscription = useCallback(() => { - navigateTo({ url: subscriptionUrl }); - }, [navigateTo, subscriptionUrl]); - return ( @@ -40,9 +37,13 @@ export const RelatedAlertsUpsell = React.memo(() => { - + {INSIGHTS_UPSELL} - + diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 21312f9c24f3f..88aebe587a8d9 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -21,6 +21,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Action } from '@kbn/ui-actions-plugin/public'; import { CellActionsProvider } from '@kbn/cell-actions'; import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { useKibana } from '../lib/kibana'; import { UpsellingProvider } from '../components/upselling_provider'; import { MockAssistantProvider } from './mock_assistant_provider'; @@ -74,25 +75,27 @@ export const TestProvidersComponent: React.FC = ({ return ( - - - ({ eui: euiDarkVars, darkMode: true })}> - - - - - Promise.resolve(cellActions)} - > - {children} - - - - - - - - + + + + ({ eui: euiDarkVars, darkMode: true })}> + + + + + Promise.resolve(cellActions)} + > + {children} + + + + + + + + + ); @@ -117,27 +120,29 @@ const TestProvidersWithPrivilegesComponent: React.FC = ({ return ( - - ({ eui: euiDarkVars, darkMode: true })}> - - - Promise.resolve(cellActions)} + + + ({ eui: euiDarkVars, darkMode: true })}> + + - {children} - - - - - + Promise.resolve(cellActions)} + > + {children} + +
+ + +
+ ); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index a384999c32b54..904ec870e9a2c 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -225,6 +225,11 @@ export class Plugin implements IPlugin; + return ; } diff --git a/x-pack/plugins/threat_intelligence/public/components/paywall.tsx b/x-pack/plugins/threat_intelligence/public/components/paywall.tsx index 8f095a2fd9baa..a01ca32a06946 100644 --- a/x-pack/plugins/threat_intelligence/public/components/paywall.tsx +++ b/x-pack/plugins/threat_intelligence/public/components/paywall.tsx @@ -6,24 +6,17 @@ */ import React, { VFC } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, -} from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { SubscriptionButtonEmpty } from '@kbn/subscription-tracking'; +import type { SubscriptionContextData } from '@kbn/subscription-tracking'; -interface PaywallProps { - /** - * Can be obtained using `http.basePath.prepend('/app/management/stack/license_management')` - */ - licenseManagementHref: string; -} +const subscriptionContext: SubscriptionContextData = { + feature: 'threat-intelligence', + source: 'security__threat-intelligence', +}; -export const Paywall: VFC = ({ licenseManagementHref }) => { +export const Paywall: VFC = () => { return ( } @@ -59,12 +52,12 @@ export const Paywall: VFC = ({ licenseManagementHref }) => {
- + - +
diff --git a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx index 1378e412b9255..98c79339ad78d 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard.tsx @@ -6,16 +6,13 @@ */ import React, { FC } from 'react'; + import { Paywall } from '../components/paywall'; -import { useKibana } from '../hooks/use_kibana'; import { useSecurityContext } from '../hooks/use_security_context'; import { SecuritySolutionPluginTemplateWrapper } from './security_solution_plugin_template_wrapper'; export const EnterpriseGuard: FC = ({ children }) => { const { licenseService } = useSecurityContext(); - const { - services: { http }, - } = useKibana(); if (licenseService.isEnterprise()) { return <>{children}; @@ -23,9 +20,7 @@ export const EnterpriseGuard: FC = ({ children }) => { return ( - + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx index 249a9d05afbc9..d13c3f561e748 100644 --- a/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/mocks/story_providers.tsx @@ -12,6 +12,7 @@ import { CoreStart, IUiSettingsClient } from '@kbn/core/public'; import { TimelinesUIStart } from '@kbn/timelines-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context'; @@ -107,7 +108,9 @@ export const StoryProvidersComponent: VFC = ({ - {children} + + {children} + diff --git a/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx index 37360284b6aa7..12c42052ee26f 100644 --- a/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/mocks/test_providers.tsx @@ -17,6 +17,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import { createTGridMocks } from '@kbn/timelines-plugin/public/mock'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; @@ -141,11 +142,13 @@ export const TestProvidersComponent: FC = ({ children }) => ( - - - {children} - - + + + + {children} + + + diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx index 49f6b3b7724bf..e30e9f92c0a5b 100755 --- a/x-pack/plugins/threat_intelligence/public/plugin.tsx +++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx @@ -11,6 +11,7 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import React, { Suspense } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking'; import { generateAttachmentType } from './modules/cases/utils/attachments'; import { KibanaContextProvider } from './hooks/use_kibana'; import { @@ -43,11 +44,16 @@ export const createApp = - - }> - - - + + + }> + + + + diff --git a/x-pack/plugins/threat_intelligence/tsconfig.json b/x-pack/plugins/threat_intelligence/tsconfig.json index 5576b8e2ea926..661186c943b54 100644 --- a/x-pack/plugins/threat_intelligence/tsconfig.json +++ b/x-pack/plugins/threat_intelligence/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", + "outDir": "target/types" }, "include": [ "common/**/*", @@ -32,9 +32,8 @@ "@kbn/utility-types", "@kbn/ui-theme", "@kbn/securitysolution-io-ts-list-types", - "@kbn/core-ui-settings-browser" + "@kbn/core-ui-settings-browser", + "@kbn/subscription-tracking" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/yarn.lock b/yarn.lock index 8416457ad4a45..a743d2ee4a6ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5829,6 +5829,10 @@ version "0.0.0" uid "" +"@kbn/subscription-tracking@link:packages/kbn-subscription-tracking": + version "0.0.0" + uid "" + "@kbn/synthetics-plugin@link:x-pack/plugins/synthetics": version "0.0.0" uid "" From be01217b1967bb31ce06f6530bdd4c0705a6e5f9 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 18 Sep 2023 12:37:02 -0400 Subject: [PATCH 22/58] [Index Management] Support data retention on Data Streams tab (#165263) --- x-pack/plugins/index_management/README.md | 29 +++- .../home/data_streams_tab.helpers.ts | 22 ++- .../home/data_streams_tab.test.ts | 44 +++--- .../common/lib/data_stream_serialization.ts | 10 +- .../common/types/data_streams.ts | 42 +++--- .../index_management/common/types/index.ts | 2 +- .../data_stream_detail_panel.tsx | 129 +++++++++++------- .../data_stream_table/data_stream_table.tsx | 33 ++++- .../api/data_streams/register_get_route.ts | 70 ++++------ .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../index_management/data_streams.ts | 9 ++ 13 files changed, 239 insertions(+), 154 deletions(-) diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md index b50309ac36099..8ac2837a683b6 100644 --- a/x-pack/plugins/index_management/README.md +++ b/x-pack/plugins/index_management/README.md @@ -49,11 +49,34 @@ POST %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7 } ``` +Create a data stream configured with data stream lifecyle. + +``` +PUT _index_template/my-index-template +{ + "index_patterns": ["my-data-stream*"], + "data_stream": { }, + "priority": 500, + "template": { + "lifecycle": { + "data_retention": "7d" + } + }, + "_meta": { + "description": "Template with data stream lifecycle" + } +} +``` + +``` +PUT _data_stream/my-data-stream +``` + ## Index templates tab ### Quick steps for testing -**Legacy index templates** are only shown in the UI on stateful *and* if a user has existing legacy index templates. You can test this functionality by creating one in Console: +**Legacy index templates** are only shown in the UI on stateful _and_ if a user has existing legacy index templates. You can test this functionality by creating one in Console: ``` PUT _template/template_1 @@ -67,6 +90,7 @@ On serverless, Elasticsearch does not support legacy index templates and therefo To test **Cloud-managed templates**: 1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools: + ``` PUT /_cluster/settings { @@ -77,6 +101,7 @@ PUT /_cluster/settings ``` 2. Create a template with the format: `.cloud-` via Dev Tools. + ``` PUT _template/.cloud-example { @@ -101,4 +126,4 @@ In 7.x, the UI supports types defined as part of the mappings for legacy index t } } } -``` \ No newline at end of file +``` diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index e38359a6d9f37..d9e6694d8ba8d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -8,7 +8,6 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { EuiDescriptionListDescription } from '@elastic/eui'; import { registerTestBed, TestBed, @@ -43,8 +42,9 @@ export interface DataStreamsTabTestBed extends TestBed { findDetailPanelTitle: () => string; findEmptyPromptIndexTemplateLink: () => ReactWrapper; findDetailPanelIlmPolicyLink: () => ReactWrapper; - findDetailPanelIlmPolicyName: () => ReactWrapper; + findDetailPanelIlmPolicyDetail: () => ReactWrapper; findDetailPanelIndexTemplateLink: () => ReactWrapper; + findDetailPanelDataRetentionDetail: () => ReactWrapper; } export const setup = async ( @@ -211,10 +211,14 @@ export const setup = async ( return find('indexTemplateLink'); }; - const findDetailPanelIlmPolicyName = () => { - const descriptionList = testBed.component.find(EuiDescriptionListDescription); - // ilm policy is the last in the details list - return descriptionList.last(); + const findDetailPanelIlmPolicyDetail = () => { + const { find } = testBed; + return find('ilmPolicyDetail'); + }; + + const findDetailPanelDataRetentionDetail = () => { + const { find } = testBed; + return find('dataRetentionDetail'); }; return { @@ -240,8 +244,9 @@ export const setup = async ( findDetailPanelTitle, findEmptyPromptIndexTemplateLink, findDetailPanelIlmPolicyLink, - findDetailPanelIlmPolicyName, + findDetailPanelIlmPolicyDetail, findDetailPanelIndexTemplateLink, + findDetailPanelDataRetentionDetail, }; }; @@ -264,6 +269,9 @@ export const createDataStreamPayload = (dataStream: Partial): DataSt delete_index: true, }, hidden: false, + lifecycle: { + data_retention: '7d', + }, ...dataStream, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 208bec0a1d6e6..f4e8221be1093 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -170,8 +170,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', '1', 'Delete'], - ['', 'dataStream2', 'green', '1', 'Delete'], + ['', 'dataStream1', 'green', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', '1', '7d', 'Delete'], ]); }); @@ -209,8 +209,8 @@ describe('Data Streams tab', () => { // The table renders with the stats columns though. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'], ]); }); @@ -229,8 +229,8 @@ describe('Data Streams tab', () => { // the human-readable string values. const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', 'Delete'], - ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', 'Delete'], + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '5b', '1', '7d', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1kb', '1', '7d', 'Delete'], ]); }); @@ -335,6 +335,12 @@ describe('Data Streams tab', () => { expect(find('summaryTab').exists()).toBeTruthy(); expect(find('title').text().trim()).toBe('indexTemplate'); }); + + test('shows data retention detail when configured', async () => { + const { actions, findDetailPanelDataRetentionDetail } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanelDataRetentionDetail().exists()).toBeTruthy(); + }); }); }); @@ -423,10 +429,10 @@ describe('Data Streams tab', () => { }); testBed.component.update(); - const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed; await actions.clickNameAt(0); expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); - expect(findDetailPanelIlmPolicyName().contains('None')).toBeTruthy(); + expect(findDetailPanelIlmPolicyDetail().exists()).toBeFalsy(); }); test('without an ILM url locator and with an ILM policy', async () => { @@ -453,10 +459,10 @@ describe('Data Streams tab', () => { }); testBed.component.update(); - const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyName } = testBed; + const { actions, findDetailPanelIlmPolicyLink, findDetailPanelIlmPolicyDetail } = testBed; await actions.clickNameAt(0); expect(findDetailPanelIlmPolicyLink().exists()).toBeFalsy(); - expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy(); + expect(findDetailPanelIlmPolicyDetail().contains('my_ilm_policy')).toBeTruthy(); }); }); @@ -489,8 +495,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'], - ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], ]); }); @@ -499,14 +505,16 @@ describe('Data Streams tab', () => { let { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', 'Delete'], - ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ['', `managed-data-stream${nonBreakingSpace}Fleet-managed`, 'green', '1', '7d', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], ]); actions.toggleViewFilterAt(0); ({ tableCellsValues } = table.getMetaData('dataStreamTable')); - expect(tableCellsValues).toEqual([['', 'non-managed-data-stream', 'green', '1', 'Delete']]); + expect(tableCellsValues).toEqual([ + ['', 'non-managed-data-stream', 'green', '1', '7d', 'Delete'], + ]); }); }); @@ -537,7 +545,7 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', 'Delete'], + ['', `hidden-data-stream${nonBreakingSpace}Hidden`, 'green', '1', '7d', 'Delete'], ]); }); }); @@ -570,8 +578,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStreamNoDelete', 'green', '1', ''], - ['', 'dataStreamWithDelete', 'green', '1', 'Delete'], + ['', 'dataStreamNoDelete', 'green', '1', '7d', ''], + ['', 'dataStreamWithDelete', 'green', '1', '7d', 'Delete'], ]); }); diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 5c27c16946325..78593b40b5eaa 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { DataStream, DataStreamFromEs, Health } from '../types'; +import { DataStream, EnhancedDataStreamFromEs, Health } from '../types'; -export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream { +export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream { const { name, timestamp_field: timeStampField, @@ -22,6 +22,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS _meta, privileges, hidden, + lifecycle, } = dataStreamFromEs; return { @@ -44,9 +45,12 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS _meta, privileges, hidden, + lifecycle, }; } -export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] { +export function deserializeDataStreamList( + dataStreamsFromEs: EnhancedDataStreamFromEs[] +): DataStream[] { return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream)); } diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index 7ac08f6684466..88e4bc237e819 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -5,20 +5,20 @@ * 2.0. */ +import { + ByteSize, + IndicesDataLifecycleWithRollover, + IndicesDataStream, + IndicesDataStreamsStatsDataStreamsStatsItem, + Metadata, +} from '@elastic/elasticsearch/lib/api/types'; + interface TimestampFieldFromEs { name: string; } type TimestampField = TimestampFieldFromEs; -interface MetaFromEs { - managed_by: string; - package: any; - managed: boolean; -} - -type Meta = MetaFromEs; - interface PrivilegesFromEs { delete_index: boolean; } @@ -27,20 +27,13 @@ type Privileges = PrivilegesFromEs; export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED'; -export interface DataStreamFromEs { - name: string; - timestamp_field: TimestampFieldFromEs; - indices: DataStreamIndexFromEs[]; - generation: number; - _meta?: MetaFromEs; - status: HealthFromEs; - template: string; - ilm_policy?: string; - store_size?: string; - store_size_bytes?: number; - maximum_timestamp?: number; - privileges: PrivilegesFromEs; - hidden: boolean; +export interface EnhancedDataStreamFromEs extends IndicesDataStream { + store_size?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size']; + store_size_bytes?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size_bytes']; + maximum_timestamp?: IndicesDataStreamsStatsDataStreamsStatsItem['maximum_timestamp']; + privileges: { + delete_index: boolean; + }; } export interface DataStreamIndexFromEs { @@ -58,12 +51,13 @@ export interface DataStream { health: Health; indexTemplateName: string; ilmPolicyName?: string; - storageSize?: string; + storageSize?: ByteSize; storageSizeBytes?: number; maxTimeStamp?: number; - _meta?: Meta; + _meta?: Metadata; privileges: Privileges; hidden: boolean; + lifecycle?: IndicesDataLifecycleWithRollover; } export interface DataStreamIndex { diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index ce5d96a842366..f3f2315cdd15b 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -13,7 +13,7 @@ export * from './mappings'; export * from './templates'; -export type { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; +export type { EnhancedDataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index d3d8b0bc64baf..28fc4a5f15218 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -41,24 +41,16 @@ interface DetailsListProps { name: string; toolTip: string; content: any; + dataTestSubj: string; }>; } const DetailsList: React.FunctionComponent = ({ details }) => { - const groups: any[] = []; - let items: any[]; + const descriptionListItems = details.map((detail, index) => { + const { name, toolTip, content, dataTestSubj } = detail; - details.forEach((detail, index) => { - const { name, toolTip, content } = detail; - - if (index % 2 === 0) { - items = []; - - groups.push({items}); - } - - items.push( - + return ( + {name} @@ -69,12 +61,27 @@ const DetailsList: React.FunctionComponent = ({ details }) => - {content} - + + {content} + + ); }); - return {groups}; + const midpoint = Math.ceil(descriptionListItems.length / 2); + const descriptionListColumnOne = descriptionListItems.slice(0, midpoint); + const descriptionListColumnTwo = descriptionListItems.slice(-midpoint); + + return ( + + + {descriptionListColumnOne} + + + {descriptionListColumnTwo} + + + ); }; interface Props { @@ -123,23 +130,64 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ ilmPolicyName, storageSize, maxTimeStamp, + lifecycle, } = dataStream; - const details = [ + + const getManagementDetails = () => { + const managementDetails = []; + + if (lifecycle?.data_retention) { + managementDetails.push({ + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionTitle', { + defaultMessage: 'Data retention', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', { + defaultMessage: 'The amount of time to retain the data in the data stream.', + }), + content: lifecycle.data_retention, + dataTestSubj: 'dataRetentionDetail', + }); + } + + if (ilmPolicyName) { + managementDetails.push({ + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', { + defaultMessage: 'Index lifecycle policy', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { + defaultMessage: `The index lifecycle policy that manages the data in the data stream.`, + }), + content: ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName + ), + dataTestSubj: 'ilmPolicyDetail', + }); + } + + return managementDetails; + }; + + const defaultDetails = [ { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', { defaultMessage: 'Health', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthToolTip', { - defaultMessage: `The health of the data stream's current backing indices`, + defaultMessage: `The health of the data stream's current backing indices.`, }), content: , + dataTestSubj: 'healthDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', { defaultMessage: 'Last updated', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', { - defaultMessage: 'The most recent document to be added to the data stream', + defaultMessage: 'The most recent document to be added to the data stream.', }), content: maxTimeStamp ? ( humanizeTimeStamp(maxTimeStamp) @@ -150,22 +198,24 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ })} ), + dataTestSubj: 'lastUpdatedDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', { defaultMessage: 'Storage size', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', { - defaultMessage: `Total size of all shards in the data stream’s backing indices`, + defaultMessage: `The total size of all shards in the data stream’s backing indices.`, }), content: storageSize, + dataTestSubj: 'storageSizeDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesTitle', { defaultMessage: 'Indices', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { - defaultMessage: `The data stream's current backing indices`, + defaultMessage: `The data stream's current backing indices.`, }), content: ( = ({ {indices.length} ), + dataTestSubj: 'indicesDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', { defaultMessage: 'Timestamp field', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip', { - defaultMessage: 'Timestamp field shared by all documents in the data stream', + defaultMessage: 'The timestamp field shared by all documents in the data stream.', }), content: timeStampField.name, + dataTestSubj: 'timestampDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationTitle', { defaultMessage: 'Generation', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationToolTip', { - defaultMessage: 'Cumulative count of backing indices created for the data stream', + defaultMessage: 'The number of backing indices generated for the data stream.', }), content: generation, + dataTestSubj: 'generationDetail', }, { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle', { @@ -202,7 +255,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateToolTip', { defaultMessage: - 'The index template that configured the data stream and configures its backing indices', + 'The index template that configured the data stream and configures its backing indices.', }), content: ( = ({ {indexTemplateName} ), - }, - { - name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', { - defaultMessage: 'Index lifecycle policy', - }), - toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { - defaultMessage: `The index lifecycle policy that manages the data stream's data`, - }), - content: - ilmPolicyName && ilmPolicyLink ? ( - - {ilmPolicyName} - - ) : ( - ilmPolicyName || ( - - {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { - defaultMessage: `None`, - })} - - ) - ), + dataTestSubj: 'indexTemplateDetail', }, ]; + const managementDetails = getManagementDetails(); + const details = [...defaultDetails, ...managementDetails]; + content = ; } diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 8e070ae7fe125..d67922f3cdcbc 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -8,7 +8,14 @@ import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiButton, + EuiLink, + EuiIcon, + EuiToolTip, +} from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; import { DataStream } from '../../../../../../common/types'; @@ -71,7 +78,6 @@ export const DataStreamTable: React.FunctionComponent = ({ render: (health: DataStream['health']) => { return ; }, - width: '100px', }); if (includeStats) { @@ -80,7 +86,6 @@ export const DataStreamTable: React.FunctionComponent = ({ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', { defaultMessage: 'Last updated', }), - width: '300px', truncateText: true, sortable: true, render: (maxTimeStamp: DataStream['maxTimeStamp']) => @@ -120,6 +125,28 @@ export const DataStreamTable: React.FunctionComponent = ({ ), }); + columns.push({ + field: 'lifecycle', + name: ( + + + {i18n.translate('xpack.idxMgmt.dataStreamList.table.dataRetentionColumnTitle', { + defaultMessage: 'Data retention', + })}{' '} + + + + ), + truncateText: true, + sortable: true, + render: (lifecycle: DataStream['lifecycle']) => lifecycle?.data_retention, + }); + columns.push({ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', { defaultMessage: 'Actions', diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index a562cc5f4cc52..0b33fb2a2b5ad 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -8,55 +8,28 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { IScopedClusterClient } from '@kbn/core/server'; +import { + IndicesDataStream, + IndicesDataStreamsStatsDataStreamsStatsItem, + SecurityHasPrivilegesResponse, +} from '@elastic/elasticsearch/lib/api/types'; import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib'; -import { DataStreamFromEs } from '../../../../common/types'; +import { EnhancedDataStreamFromEs } from '../../../../common/types'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '..'; -interface PrivilegesFromEs { - username: string; - has_all_requested: boolean; - cluster: Record; - index: Record>; - application: Record; -} - -interface StatsFromEs { - data_stream: string; - store_size: string; - store_size_bytes: number; - maximum_timestamp: number; -} - const enhanceDataStreams = ({ dataStreams, dataStreamsStats, dataStreamsPrivileges, }: { - dataStreams: DataStreamFromEs[]; - dataStreamsStats?: StatsFromEs[]; - dataStreamsPrivileges?: PrivilegesFromEs; -}): DataStreamFromEs[] => { - return dataStreams.map((dataStream: DataStreamFromEs) => { - let enhancedDataStream = { ...dataStream }; - - if (dataStreamsStats) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { store_size, store_size_bytes, maximum_timestamp } = - dataStreamsStats.find( - ({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name - ) || {}; - - enhancedDataStream = { - ...enhancedDataStream, - store_size, - store_size_bytes, - maximum_timestamp, - }; - } - - enhancedDataStream = { - ...enhancedDataStream, + dataStreams: IndicesDataStream[]; + dataStreamsStats?: IndicesDataStreamsStatsDataStreamsStatsItem[]; + dataStreamsPrivileges?: SecurityHasPrivilegesResponse; +}): EnhancedDataStreamFromEs[] => { + return dataStreams.map((dataStream) => { + const enhancedDataStream: EnhancedDataStreamFromEs = { + ...dataStream, privileges: { delete_index: dataStreamsPrivileges ? dataStreamsPrivileges.index[dataStream.name].delete_index @@ -64,6 +37,17 @@ const enhanceDataStreams = ({ }, }; + if (dataStreamsStats) { + const currentDataStreamStats: IndicesDataStreamsStatsDataStreamsStatsItem | undefined = + dataStreamsStats.find(({ data_stream: statsName }) => statsName === dataStream.name); + + if (currentDataStreamStats) { + enhancedDataStream.store_size = currentDataStreamStats.store_size; + enhancedDataStream.store_size_bytes = currentDataStreamStats.store_size_bytes; + enhancedDataStream.maximum_timestamp = currentDataStreamStats.maximum_timestamp; + } + } + return enhancedDataStream; }); }; @@ -125,11 +109,8 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }: } const enhancedDataStreams = enhanceDataStreams({ - // @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem dataStreams, - // @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem dataStreamsStats, - // @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges dataStreamsPrivileges, }); @@ -164,11 +145,8 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }: } const enhancedDataStreams = enhanceDataStreams({ - // @ts-expect-error DataStreamFromEs conflicts with @elastic/elasticsearch IndicesGetDataStreamIndicesGetDataStreamItem dataStreams, - // @ts-expect-error StatsFromEs conflicts with @elastic/elasticsearch IndicesDataStreamsStatsDataStreamsStatsItem dataStreamsStats, - // @ts-expect-error PrivilegesFromEs conflicts with @elastic/elasticsearch ApplicationsPrivileges dataStreamsPrivileges, }); const body = deserializeDataStream(enhancedDataStreams[0]); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index fb4219e6052f4..50e6763aad805 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17347,7 +17347,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "Nombre cumulatif d'index de sauvegarde créés pour le flux de données", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "Intégrité", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "Intégrité des index de sauvegarde actuels du flux de données", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "Aucun", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "Stratégie de cycle de vie des index", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "Stratégie de cycle de vie de l'index qui gère les données du flux de données", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "Modèle d'index", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9c1bd25593a47..d2b3c60becf20 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17361,7 +17361,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "データストリームに作成されたバッキングインデックスの累積数", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "ヘルス", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "データストリームの現在のバッキングインデックスのヘルス", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "なし", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "インデックスライフサイクルポリシー", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "データストリームのデータを管理するインデックスライフサイクルポリシー", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "インデックステンプレート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 75fcb531da66a..21a7856a7baa3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17361,7 +17361,6 @@ "xpack.idxMgmt.dataStreamDetailPanel.generationToolTip": "为数据流创建的后备索引的累积计数", "xpack.idxMgmt.dataStreamDetailPanel.healthTitle": "运行状况", "xpack.idxMgmt.dataStreamDetailPanel.healthToolTip": "数据流的当前后备索引的运行状况", - "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage": "无", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle": "索引生命周期策略", "xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip": "用于管理数据流数据的索引生命周期策略", "xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle": "索引模板", diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index 0117204343285..520396ad46283 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -112,6 +112,9 @@ export default function ({ getService }: FtrProviderContext) { expect(testDataStream).to.eql({ name: testDataStreamName, + lifecycle: { + enabled: true, + }, privileges: { delete_index: true, }, @@ -166,6 +169,9 @@ export default function ({ getService }: FtrProviderContext) { indexTemplateName: testDataStreamName, maxTimeStamp: 0, hidden: false, + lifecycle: { + enabled: true, + }, }); }); @@ -197,6 +203,9 @@ export default function ({ getService }: FtrProviderContext) { indexTemplateName: testDataStreamName, maxTimeStamp: 0, hidden: false, + lifecycle: { + enabled: true, + }, }); }); }); From 3b25d5adad78554db78f836ffbebfce04712bf2d Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 18 Sep 2023 12:46:46 -0400 Subject: [PATCH 23/58] [Ingest Pipelines] Re-enable API integration test (#166115) --- .../apis/management/ingest_pipelines/lib/api.ts | 8 +++++++- .../test_suites/common/ingest_pipelines.ts | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts index 1e185b88b7587..04d157e79fe65 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/api.ts @@ -55,8 +55,14 @@ export function IngestPipelinesAPIProvider({ getService }: FtrProviderContext) { }, async createIndex(index: { index: string; id: string; body: object }) { - log.debug(`Creating index: '${index.index}'`); + const indexExists = await es.indices.exists({ index: index.index }); + + // Index should not exist, but in the case that it already does, we bypass the create request + if (indexExists) { + return; + } + log.debug(`Creating index: '${index.index}'`); return await es.index(index); }, diff --git a/x-pack/test_serverless/api_integration/test_suites/common/ingest_pipelines.ts b/x-pack/test_serverless/api_integration/test_suites/common/ingest_pipelines.ts index 3a1ffba020246..1e5ee6d39bb71 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/ingest_pipelines.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/ingest_pipelines.ts @@ -350,8 +350,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/165539 - describe.skip('Fetch documents', () => { + describe('Fetch documents', () => { const INDEX = 'test_index'; const DOCUMENT_ID = '1'; const DOCUMENT = { From 2a4cb333eca912c15c794402f934352e7df73842 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Mon, 18 Sep 2023 13:33:23 -0400 Subject: [PATCH 24/58] fix(tests): serverless observability flaky test (#166205) --- .../test_suites/observability/cases/configure.ts | 6 ++++-- .../test_suites/observability/landing_page.ts | 12 ++++++++++-- .../test_suites/observability/navigation.ts | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index e0296f26e773e..91d5072a8162c 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -24,8 +24,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); - - await common.clickAndValidate('configure-case-button', 'case-configure-title'); }); after(async () => { @@ -34,6 +32,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); describe('Closure options', function () { + before(async () => { + await common.clickAndValidate('configure-case-button', 'case-configure-title'); + }); + it('defaults the closure option correctly', async () => { await cases.common.assertRadioGroupValue('closure-options-radio-group', 'close-by-user'); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts index 08b3879d5f6d3..68263e5ce2c39 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/landing_page.ts @@ -9,11 +9,19 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlObltNavigation = getService('svlObltNavigation'); const SvlObltOnboardingStreamLogFilePage = getPageObject('SvlObltOnboardingStreamLogFilePage'); - // FLAKY: https://github.com/elastic/kibana/issues/165885 - describe.skip('landing page', function () { + describe('landing page', function () { + before(async () => { + await svlCommonPage.login(); + }); + + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('has quickstart badge', async () => { await svlObltNavigation.navigateToLandingPage(); await svlObltOnboardingPage.assertQuickstartBadgeExists(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index b2761fbaa7f3e..57b636efa6a75 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -11,15 +11,20 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObject, getService }: FtrProviderContext) { const svlObltOnboardingPage = getPageObject('svlObltOnboardingPage'); const svlObltNavigation = getService('svlObltNavigation'); + const svlCommonPage = getPageObject('svlCommonPage'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); const browser = getService('browser'); - // Failing: See https://github.com/elastic/kibana/issues/165924 - describe.skip('navigation', function () { + describe('navigation', function () { before(async () => { + await svlCommonPage.login(); await svlObltNavigation.navigateToLandingPage(); }); + after(async () => { + await svlCommonPage.forceLogout(); + }); + it('navigate observability sidenav & breadcrumbs', async () => { const expectNoPageReload = await svlCommonNavigation.createNoPageReloadCheck(); From 6d47334528b8f409eb1ab10c3e9a0f679dc61d86 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 18 Sep 2023 11:45:06 -0600 Subject: [PATCH 25/58] [Event annotations] add more API integration tests (#166463) ## Summary Covers the event annotations API in tests. Part of https://github.com/elastic/kibana/issues/159053 Part of https://github.com/elastic/kibana/issues/161038 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../content_management/v1/cm_services.ts | 1 - .../common/content_management/v1/types.ts | 2 - src/plugins/event_annotation/common/index.ts | 6 + .../event_annotation_group_storage.ts | 1 - .../event_annotations/event_annotations.ts | 358 +++++++++++++++++- .../event_annotations/event_annotations.json | 98 ++++- test/tsconfig.json | 3 +- 7 files changed, 435 insertions(+), 34 deletions(-) diff --git a/src/plugins/event_annotation/common/content_management/v1/cm_services.ts b/src/plugins/event_annotation/common/content_management/v1/cm_services.ts index 8da6efc7c7f08..f5f7b0ac83b0e 100644 --- a/src/plugins/event_annotation/common/content_management/v1/cm_services.ts +++ b/src/plugins/event_annotation/common/content_management/v1/cm_services.ts @@ -79,7 +79,6 @@ const getResultSchema = schema.object( ); const createOptionsSchema = schema.object({ - overwrite: schema.maybe(schema.boolean()), references: schema.maybe(referencesSchema), }); diff --git a/src/plugins/event_annotation/common/content_management/v1/types.ts b/src/plugins/event_annotation/common/content_management/v1/types.ts index 67ba080a78151..5996a6f0db455 100644 --- a/src/plugins/event_annotation/common/content_management/v1/types.ts +++ b/src/plugins/event_annotation/common/content_management/v1/types.ts @@ -79,8 +79,6 @@ export type EventAnnotationGroupGetOut = GetResult< // ----------- CREATE -------------- export interface CreateOptions { - /** If a document with the given `id` already exists, overwrite it's contents (default=false). */ - overwrite?: boolean; /** Array of referenced saved objects. */ references?: Reference[]; } diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index cb30e43060cfb..a99204be39694 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -27,10 +27,16 @@ export type { FetchEventAnnotationsArgs } from './fetch_event_annotations/types' export type { EventAnnotationArgs, EventAnnotationOutput } from './types'; export type { + EventAnnotationGroupGetIn, + EventAnnotationGroupGetOut, EventAnnotationGroupSavedObjectAttributes, EventAnnotationGroupCreateIn, + EventAnnotationGroupCreateOut, EventAnnotationGroupUpdateIn, EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut, + EventAnnotationGroupDeleteIn, + EventAnnotationGroupDeleteOut, } from './content_management'; export { CONTENT_ID } from './content_management'; export { ANNOTATIONS_LISTING_VIEW_ID } from './constants'; diff --git a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts index f71e9cd72b43b..dcb25deb71140 100644 --- a/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts +++ b/src/plugins/event_annotation/server/content_management/event_annotation_group_storage.ts @@ -121,7 +121,6 @@ export class EventAnnotationGroupStorage const transforms = getTransforms(cmServicesDefinition, requestVersion); const soClient = await savedObjectClientFromRequest(ctx); - // Save data in DB const { saved_object: savedObject, alias_purpose: aliasPurpose, diff --git a/test/api_integration/apis/event_annotations/event_annotations.ts b/test/api_integration/apis/event_annotations/event_annotations.ts index fda97881f3d9b..fc4a313279b02 100644 --- a/test/api_integration/apis/event_annotations/event_annotations.ts +++ b/test/api_integration/apis/event_annotations/event_annotations.ts @@ -10,18 +10,29 @@ import expect from '@kbn/expect'; import type { EventAnnotationGroupSavedObjectAttributes, EventAnnotationGroupCreateIn, + EventAnnotationGroupCreateOut, EventAnnotationGroupUpdateIn, EventAnnotationGroupSearchIn, + EventAnnotationGroupSearchOut, + EventAnnotationGroupGetIn, + EventAnnotationGroupGetOut, + EventAnnotationGroupDeleteIn, + EventAnnotationGroupDeleteOut, } from '@kbn/event-annotation-plugin/common'; import { CONTENT_ID } from '@kbn/event-annotation-plugin/common'; +import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common'; import { FtrProviderContext } from '../../ftr_provider_context'; const CONTENT_ENDPOINT = '/api/content_management/rpc'; const API_VERSION = 1; -const EXISTING_ID_1 = 'fcebef20-3ba4-11ee-85d3-3dd00bdd66ef'; // from loaded archive -const EXISTING_ID_2 = '0d1aa670-3baf-11ee-a4a7-c11cb33a9549'; // from loaded archive +// IDs come from from loaded archive +const EXISTING_ID_1 = '46c2a460-4e77-11ee-bb97-116581699678'; +const EXISTING_ID_2 = '425d2760-4e77-11ee-bb97-116581699678'; +const DESCRIPTION_2 = 'i am a description you can search for!'; +const EXISTING_ID_3 = '3905a4d0-4e77-11ee-bb97-116581699678'; +const TAG_ID = '36a8f020-4e77-11ee-bb97-116581699678'; const DEFAULT_EVENT_ANNOTATION_GROUP: EventAnnotationGroupSavedObjectAttributes = { title: 'a group', @@ -56,6 +67,8 @@ export default function ({ getService }: FtrProviderContext) { describe('group API', () => { before(async () => { + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + await kibanaServer.importExport.load( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); @@ -65,10 +78,79 @@ export default function ({ getService }: FtrProviderContext) { await kibanaServer.importExport.unload( 'test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json' ); + + await kibanaServer.savedObjects.clean({ types: [EVENT_ANNOTATION_GROUP_TYPE] }); + }); + + describe('get', () => { + it(`should retrieve an existing group`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: EXISTING_ID_1, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupGetOut; + + expect(result.item.id).to.be(EXISTING_ID_1); + expect(result.meta.outcome).to.be('exactMatch'); + expect(result.item.references.length).to.be(1); + expect(result.item.attributes).to.eql({ + annotations: [ + { + filter: { + language: 'kuery', + query: + 'agent.keyword : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" ', + type: 'kibana_query', + }, + icon: 'triangle', + id: 'fdede168-eff1-400f-b106-0f62061f5099', + key: { + type: 'point_in_time', + }, + label: 'Event', + timeField: 'timestamp', + type: 'query', + }, + ], + dataViewSpec: null, + description: '', + ignoreGlobalFilters: true, + title: 'group3', + }); + }); + + it(`should reject a group that does not exist`, async () => { + const payload: EventAnnotationGroupGetIn = { + contentTypeId: CONTENT_ID, + id: 'does-not-exist', + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/get`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); }); describe('search', () => { - // TODO test tag searching, ordering, pagination, etc + const performSearch = (payload: EventAnnotationGroupSearchIn) => + supertest.post(`${CONTENT_ENDPOINT}/search`).set('kbn-xsrf', 'kibana').send(payload); it(`should retrieve existing groups`, async () => { const payload: EventAnnotationGroupSearchIn = { @@ -83,19 +165,172 @@ export default function ({ getService }: FtrProviderContext) { version: API_VERSION, }; + const resp = await performSearch(payload).expect(200); + + const results = resp.body.result.result.hits; + expect(results.length).to.be(3); + expect(results.map(({ id }: { id: string }) => id)).to.eql([ + EXISTING_ID_1, + EXISTING_ID_2, + EXISTING_ID_3, + ]); + }); + + it(`should filter by tag`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + tags: { + included: [TAG_ID], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(2); + expect( + result.hits.every(({ references }) => references.map(({ id }) => id).includes(TAG_ID)) + ).to.be(true); + }); + + it(`should filter by text`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1000, + text: DESCRIPTION_2, + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_2); + }); + + it(`should paginate`, async () => { + const payload: EventAnnotationGroupSearchIn = { + contentTypeId: CONTENT_ID, + query: { + limit: 1, + cursor: '1', + tags: { + included: [], + excluded: [], + }, + }, + version: API_VERSION, + }; + + const resp = await performSearch(payload).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupSearchOut; + expect(result.hits.length).to.be(1); + expect(result.hits[0].id).to.be(EXISTING_ID_1); + expect(result.pagination.total).to.be(3); + + // get second page + payload.query.cursor = '2'; + + const resp2 = await performSearch(payload).expect(200); + + const result2 = resp2.body.result.result as EventAnnotationGroupSearchOut; + expect(result2.hits.length).to.be(1); + expect(result2.hits[0].id).to.be(EXISTING_ID_2); + expect(result2.pagination.total).to.be(3); + + // get third page + payload.query.cursor = '3'; + + const resp3 = await performSearch(payload).expect(200); + + const result3 = resp3.body.result.result as EventAnnotationGroupSearchOut; + expect(result3.hits.length).to.be(1); + expect(result3.hits[0].id).to.be(EXISTING_ID_3); + expect(result3.pagination.total).to.be(3); + }); + }); + + describe('create', () => { + it(`should create a new group`, async () => { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: DEFAULT_EVENT_ANNOTATION_GROUP, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + const resp = await supertest - .post(`${CONTENT_ENDPOINT}/search`) + .post(`${CONTENT_ENDPOINT}/create`) .set('kbn-xsrf', 'kibana') .send(payload) .expect(200); - const results = resp.body.result.result.hits; - expect(results.length).to.be(2); - expect(results.map(({ id }: { id: string }) => id)).to.eql([EXISTING_ID_2, EXISTING_ID_1]); + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql(DEFAULT_EVENT_ANNOTATION_GROUP); + expect(result.item.id).to.be.a('string'); + expect(result.item.namespaces).to.eql(['default']); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupCreateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/create`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } }); - }); - describe('create', () => { it(`should require dataViewSpec to be specified`, async () => { const createWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupCreateIn = { @@ -127,6 +362,81 @@ export default function ({ getService }: FtrProviderContext) { }); describe('update', () => { + it(`should update a group`, async () => { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }, + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(200); + + const result = resp.body.result.result as EventAnnotationGroupCreateOut; + + expect(result.item.attributes).to.eql({ + ...DEFAULT_EVENT_ANNOTATION_GROUP, + description: 'updated description', + }); + expect(result.item.id).to.be(EXISTING_ID_1); + }); + + it(`should reject malformed groups`, async () => { + const badGroups = [ + // extra property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + extraProp: 'some-value', + }, + // missing title + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + title: undefined, + }, + // wrong type for property + { + ...DEFAULT_EVENT_ANNOTATION_GROUP, + ignoreGlobalFilters: 'not-a-boolean', + }, + ] as unknown as EventAnnotationGroupSavedObjectAttributes[]; // (coerce the types because these are intentionally malformed) + + const expectedMessages = [ + 'Invalid data. [extraProp]: definition for this key is missing', + 'Invalid data. [title]: expected value of type [string] but got [undefined]', + 'Invalid data. [ignoreGlobalFilters]: expected value of type [boolean] but got [string]', + ]; + + for (let i = 0; i < badGroups.length; i++) { + const payload: EventAnnotationGroupUpdateIn = { + contentTypeId: CONTENT_ID, + data: badGroups[i], + id: EXISTING_ID_1, + options: { + references: DEFAULT_REFERENCES, + }, + version: API_VERSION, + }; + + const resp = await supertest + .post(`${CONTENT_ENDPOINT}/update`) + .set('kbn-xsrf', 'kibana') + .send(payload) + .expect(400); + + expect(resp.body.message).to.be(expectedMessages[i]); + } + }); + it(`should require dataViewSpec to be specified`, async () => { const updateWithDataViewSpec = (dataViewSpec: any) => { const payload: EventAnnotationGroupUpdateIn = { @@ -158,6 +468,34 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // TODO - delete + describe('delete', () => { + const deleteGroupByID = (id: string) => { + const payload: EventAnnotationGroupDeleteIn = { + contentTypeId: CONTENT_ID, + id, + version: API_VERSION, + }; + + return supertest.post(`${CONTENT_ENDPOINT}/delete`).set('kbn-xsrf', 'kibana').send(payload); + }; + + it(`should delete a group`, async () => { + const resp = await deleteGroupByID(EXISTING_ID_1).expect(200); + + const result = resp.body.result.result as EventAnnotationGroupDeleteOut; + + expect(result.success).to.be(true); + }); + + it(`should reject deleting a group that does not exist`, async () => { + const resp = await deleteGroupByID('does-not-exist').expect(404); + + expect(resp.body).to.eql({ + error: 'Not Found', + message: 'Saved object [event-annotation-group/does-not-exist] not found', + statusCode: 404, + }); + }); + }); }); } diff --git a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json index ed574c749518c..4732d061c6930 100644 --- a/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json +++ b/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json @@ -7,33 +7,31 @@ "title": "kibana_sample_data_logs" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:49:25.494Z", + "created_at": "2023-09-07T17:23:20.906Z", "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "managed": false, "references": [], "type": "index-pattern", "typeMigrationVersion": "8.0.0", - "updated_at": "2023-08-15T19:49:25.494Z", - "version": "WzIyLDFd" + "updated_at": "2023-09-07T17:23:20.906Z", + "version": "WzEwMiwxXQ==" } { "attributes": { "annotations": [ { - "color": "#6092c0", "filter": { "language": "kuery", "query": "agent.keyword : \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\" ", "type": "kibana_query" }, - "icon": "asterisk", - "id": "499ee351-f541-46e0-b327-b3dcae91aff5", + "icon": "triangle", + "id": "fdede168-eff1-400f-b106-0f62061f5099", "key": { "type": "point_in_time" }, "label": "Event", - "lineStyle": "dashed", "timeField": "timestamp", "type": "query" } @@ -41,11 +39,11 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "Another group" + "title": "group3" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T21:02:32.023Z", - "id": "0d1aa670-3baf-11ee-a4a7-c11cb33a9549", + "created_at": "2023-09-08T18:41:09.030Z", + "id": "46c2a460-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { @@ -55,8 +53,25 @@ } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:15:43.724Z", - "version": "WzU4LDFd" + "updated_at": "2023-09-08T18:42:48.080Z", + "version": "WzE2OCwxXQ==" +} + +{ + "attributes": { + "color": "#dac7c4", + "description": "a tag to filter by", + "name": "tag" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:40:42.018Z", + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2023-09-08T18:40:42.018Z", + "version": "WzI3NDUsMV0=" } { @@ -64,9 +79,49 @@ "annotations": [ { "icon": "triangle", - "id": "1d9627a8-11dc-44f1-badb-4d40a80b6bee", + "id": "3d9f03ca-36aa-4ebb-aab8-efef1591c6d5", "key": { - "timestamp": "2023-08-10T15:00:00.000Z", + "timestamp": "2023-09-08T18:32:00.000Z", + "type": "point_in_time" + }, + "label": "Event", + "type": "manual" + } + ], + "dataViewSpec": null, + "description": "i am a description you can search for!", + "ignoreGlobalFilters": true, + "title": "group2" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-09-08T18:41:01.654Z", + "id": "425d2760-4e77-11ee-bb97-116581699678", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", + "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" + } + ], + "type": "event-annotation-group", + "updated_at": "2023-09-08T18:41:01.654Z", + "version": "WzE2NCwxXQ==" +} + +{ + "attributes": { + "annotations": [ + { + "icon": "triangle", + "id": "f7be288b-8adf-48b9-89d4-267db4863a3d", + "key": { + "timestamp": "2023-09-08T18:32:00.000Z", "type": "point_in_time" }, "label": "Event", @@ -76,20 +131,25 @@ "dataViewSpec": null, "description": "", "ignoreGlobalFilters": true, - "title": "A group" + "title": "group1" }, "coreMigrationVersion": "8.8.0", - "created_at": "2023-08-15T19:50:29.907Z", - "id": "fcebef20-3ba4-11ee-85d3-3dd00bdd66ef", + "created_at": "2023-09-08T18:40:45.981Z", + "id": "3905a4d0-4e77-11ee-bb97-116581699678", "managed": false, "references": [ { "id": "90943e30-9a47-11e8-b64d-95841ca0b247", "name": "event-annotation-group_dataView-ref-90943e30-9a47-11e8-b64d-95841ca0b247", "type": "index-pattern" + }, + { + "id": "36a8f020-4e77-11ee-bb97-116581699678", + "name": "36a8f020-4e77-11ee-bb97-116581699678", + "type": "tag" } ], "type": "event-annotation-group", - "updated_at": "2023-08-15T22:13:19.290Z", - "version": "WzU0LDFd" + "updated_at": "2023-09-08T18:40:45.981Z", + "version": "WzE2MiwxXQ==" } \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json index 56d4f185930e7..d71c7f9c8ffb1 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -68,6 +68,7 @@ "@kbn/core-saved-objects-server", "@kbn/discover-plugin", "@kbn/core-http-common", - "@kbn/event-annotation-plugin" + "@kbn/event-annotation-plugin", + "@kbn/event-annotation-common" ] } From 707fbf115a9a6d889a645316c94be5b288e970c6 Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Mon, 18 Sep 2023 13:45:25 -0400 Subject: [PATCH 26/58] [DOCS] Note Kibana 8.10.0 was withdrawn (#166644) --- docs/CHANGELOG.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 045cdb5e80bd7..297a347e721ba 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -72,6 +72,8 @@ Presentation:: [[release-notes-8.10.0]] == {kib} 8.10.0 +IMPORTANT: {kib} 8.10.0 has been withdrawn. + For information about the {kib} 8.10.0 release, review the following information. [float] From 70a2f4cdb5deca6e762a2c88b0d919d7560eac30 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:56:00 -0700 Subject: [PATCH 27/58] Mobile UI crash widget (#163527) ## Summary Implemented crash widget & most crashes by location widget in the mobile landing page in APM. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/lib/apm/mobile_device.ts | 1 + .../service_overview/stats/location_stats.tsx | 11 +- .../mobile/service_overview/stats/stats.tsx | 20 +-- .../routes/mobile/get_mobile_crash_rate.ts | 165 ++++++++++++++++++ .../mobile/get_mobile_crashes_by_location.ts | 109 ++++++++++++ .../mobile/get_mobile_location_stats.ts | 11 +- .../server/routes/mobile/get_mobile_stats.ts | 18 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../mobile/mobile_location_stats.spec.ts | 14 ++ .../tests/mobile/mobile_stats.spec.ts | 26 ++- 12 files changed, 354 insertions(+), 24 deletions(-) create mode 100644 x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts create mode 100644 x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts index 252590104e7a2..9a904d0d94dbb 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts @@ -254,6 +254,7 @@ export class MobileDevice extends Entity { return new ApmError({ ...this.fields, 'error.type': 'crash', + 'error.id': generateLongId(message), 'error.exception': [{ message, ...{ type: 'crash' } }], 'error.grouping_name': groupingName || message, }); diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx index 731205c4715a9..22e95e54817dd 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/location_stats.tsx @@ -133,17 +133,18 @@ export function MobileLocationStats({ trendShape: MetricTrendShape.Area, }, { - color: euiTheme.eui.euiColorDisabled, + color: euiTheme.eui.euiColorLightestShade, title: i18n.translate('xpack.apm.mobile.location.metrics.crashes', { defaultMessage: 'Most crashes', }), - subtitle: i18n.translate('xpack.apm.mobile.coming.soon', { - defaultMessage: 'Coming Soon', + extra: getComparisonValueFormatter({ + currentPeriodValue: currentPeriod?.mostCrashes.value, + previousPeriodValue: previousPeriod?.mostCrashes.value, }), icon: getIcon('bug'), - value: NOT_AVAILABLE_LABEL, + value: currentPeriod?.mostCrashes.location ?? NOT_AVAILABLE_LABEL, valueFormatter: (value) => `${value}`, - trend: [], + trend: currentPeriod?.mostCrashes.timeseries, trendShape: MetricTrendShape.Area, }, { diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx index 707b759631d89..3e8d42d5a2a3c 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import React, { useCallback } from 'react'; import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params'; import { useFetcher, @@ -104,17 +105,16 @@ export function MobileStats({ const metrics: MetricDatum[] = [ { - color: euiTheme.eui.euiColorDisabled, + color: euiTheme.eui.euiColorLightestShade, title: i18n.translate('xpack.apm.mobile.metrics.crash.rate', { - defaultMessage: 'Crash Rate (Crash per minute)', - }), - subtitle: i18n.translate('xpack.apm.mobile.coming.soon', { - defaultMessage: 'Coming Soon', + defaultMessage: 'Crash rate', }), icon: getIcon('bug'), - value: 'N/A', - valueFormatter: (value: number) => valueFormatter(value), - trend: [], + value: data?.currentPeriod?.crashRate?.value ?? NOT_AVAILABLE_LABEL, + valueFormatter: (value: number) => + valueFormatter(Number((value * 100).toPrecision(2)), '%'), + trend: data?.currentPeriod?.crashRate?.timeseries, + extra: getComparisonValueFormatter(data?.previousPeriod.crashRate?.value), trendShape: MetricTrendShape.Area, }, { @@ -137,7 +137,7 @@ export function MobileStats({ defaultMessage: 'Sessions', }), icon: getIcon('timeslider'), - value: data?.currentPeriod?.sessions?.value ?? NaN, + value: data?.currentPeriod?.sessions?.value ?? NOT_AVAILABLE_LABEL, valueFormatter: (value: number) => valueFormatter(value), trend: data?.currentPeriod?.sessions?.timeseries, extra: getComparisonValueFormatter(data?.previousPeriod.sessions?.value), @@ -149,7 +149,7 @@ export function MobileStats({ defaultMessage: 'HTTP requests', }), icon: getIcon('kubernetesPod'), - value: data?.currentPeriod?.requests?.value ?? NaN, + value: data?.currentPeriod?.requests?.value ?? NOT_AVAILABLE_LABEL, extra: getComparisonValueFormatter(data?.previousPeriod.requests?.value), valueFormatter: (value: number) => valueFormatter(value), trend: data?.currentPeriod?.requests?.timeseries, diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts new file mode 100644 index 0000000000000..bf498bf704607 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crash_rate.ts @@ -0,0 +1,165 @@ +/* + * 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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { Coordinate } from '../../../typings/timeseries'; +import { Maybe } from '../../../typings/common'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { getBucketSize } from '../../../common/utils/get_bucket_size'; +import { + ERROR_TYPE, + ERROR_ID, + SERVICE_NAME, +} from '../../../common/es_fields/apm'; +import { environmentQuery } from '../../../common/utils/environment_query'; +import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; + +export interface CrashRateTimeseries { + currentPeriod: { timeseries: Coordinate[]; value: Maybe }; + previousPeriod: { timeseries: Coordinate[]; value: Maybe }; +} + +interface Props { + apmEventClient: APMEventClient; + serviceName: string; + transactionName?: string; + environment: string; + start: number; + end: number; + kuery: string; + offset?: string; +} + +async function getMobileCrashTimeseries({ + apmEventClient, + serviceName, + transactionName, + environment, + start, + end, + kuery, + offset, +}: Props) { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + minBucketSize: 60, + }); + + const aggs = { + crashes: { + cardinality: { field: ERROR_ID }, + }, + }; + + const response = await apmEventClient.search('get_mobile_crash_rate', { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(ERROR_TYPE, 'crash'), + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { min: startWithOffset, max: endWithOffset }, + }, + aggs, + }, + ...aggs, + }, + }, + }); + + const timeseries = + response?.aggregations?.timeseries.buckets.map((bucket) => { + return { + x: bucket.key, + y: bucket.crashes.value, + }; + }) ?? []; + + return { + timeseries, + value: response.aggregations?.crashes?.value, + }; +} + +export async function getMobileCrashRate({ + kuery, + apmEventClient, + serviceName, + transactionName, + environment, + start, + end, + offset, +}: Props): Promise { + const options = { + serviceName, + transactionName, + apmEventClient, + kuery, + environment, + }; + + const currentPeriodPromise = getMobileCrashTimeseries({ + ...options, + start, + end, + }); + + const previousPeriodPromise = offset + ? getMobileCrashTimeseries({ + ...options, + start, + end, + offset, + }) + : { timeseries: [], value: null }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + return { + currentPeriod, + previousPeriod: { + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.timeseries, + previousPeriodTimeseries: previousPeriod.timeseries, + }), + value: previousPeriod?.value, + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts new file mode 100644 index 0000000000000..4117e6a220bd4 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_crashes_by_location.ts @@ -0,0 +1,109 @@ +/* + * 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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { SERVICE_NAME, ERROR_TYPE } from '../../../common/es_fields/apm'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { getBucketSize } from '../../../common/utils/get_bucket_size'; +import { environmentQuery } from '../../../common/utils/environment_query'; + +interface Props { + kuery: string; + apmEventClient: APMEventClient; + serviceName: string; + environment: string; + start: number; + end: number; + locationField?: string; + offset?: string; +} + +export async function getCrashesByLocation({ + kuery, + apmEventClient, + serviceName, + environment, + start, + end, + locationField, + offset, +}: Props) { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + minBucketSize: 60, + }); + + const aggs = { + crashes: { + filter: { term: { [ERROR_TYPE]: 'crash' } }, + aggs: { + crashesByLocation: { + terms: { + field: locationField, + }, + }, + }, + }, + }; + const response = await apmEventClient.search('get_mobile_location_crashes', { + apm: { + events: [ProcessorEvent.error], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + }, + aggs, + }, + ...aggs, + }, + }, + }); + return { + location: response.aggregations?.crashes?.crashesByLocation?.buckets[0] + ?.key as string, + value: + response.aggregations?.crashes?.crashesByLocation?.buckets[0] + ?.doc_count ?? 0, + timeseries: + response.aggregations?.timeseries?.buckets.map((bucket) => ({ + x: bucket.key, + y: + response.aggregations?.crashes?.crashesByLocation?.buckets[0] + ?.doc_count ?? 0, + })) ?? [], + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts index 6a43a9965a03f..ccc777e3cfe31 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_location_stats.ts @@ -9,6 +9,7 @@ import { CLIENT_GEO_COUNTRY_NAME } from '../../../common/es_fields/apm'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getSessionsByLocation } from './get_mobile_sessions_by_location'; import { getHttpRequestsByLocation } from './get_mobile_http_requests_by_location'; +import { getCrashesByLocation } from './get_mobile_crashes_by_location'; import { Maybe } from '../../../typings/common'; export type Timeseries = Array<{ x: number; y: number }>; @@ -24,6 +25,11 @@ interface LocationStats { value: Maybe; timeseries: Timeseries; }; + mostCrashes: { + location?: string; + value: Maybe; + timeseries: Timeseries; + }; } export interface MobileLocationStats { @@ -63,14 +69,16 @@ async function getMobileLocationStats({ offset, }; - const [mostSessions, mostRequests] = await Promise.all([ + const [mostSessions, mostRequests, mostCrashes] = await Promise.all([ getSessionsByLocation({ ...commonProps }), getHttpRequestsByLocation({ ...commonProps }), + getCrashesByLocation({ ...commonProps }), ]); return { mostSessions, mostRequests, + mostCrashes, }; } @@ -108,6 +116,7 @@ export async function getMobileLocationStatsPeriods({ : { mostSessions: { value: null, timeseries: [] }, mostRequests: { value: null, timeseries: [] }, + mostCrashes: { value: null, timeseries: [] }, }; const [currentPeriod, previousPeriod] = await Promise.all([ diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts index 70d51360ff89d..071487298ab7a 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts @@ -9,6 +9,7 @@ import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_ev import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getMobileSessions } from './get_mobile_sessions'; import { getMobileHttpRequests } from './get_mobile_http_requests'; +import { getMobileCrashRate } from './get_mobile_crash_rate'; import { Maybe } from '../../../typings/common'; export interface Timeseries { @@ -18,6 +19,7 @@ export interface Timeseries { interface MobileStats { sessions: { timeseries: Timeseries[]; value: Maybe }; requests: { timeseries: Timeseries[]; value: Maybe }; + crashRate: { timeseries: Timeseries[]; value: Maybe }; } export interface MobilePeriodStats { @@ -60,9 +62,10 @@ async function getMobileStats({ offset, }; - const [sessions, httpRequests] = await Promise.all([ + const [sessions, httpRequests, crashes] = await Promise.all([ getMobileSessions({ ...commonProps }), getMobileHttpRequests({ ...commonProps }), + getMobileCrashRate({ ...commonProps }), ]); return { @@ -74,6 +77,18 @@ async function getMobileStats({ value: httpRequests.currentPeriod.value, timeseries: httpRequests.currentPeriod.timeseries as Timeseries[], }, + crashRate: { + value: sessions.currentPeriod.value + ? (crashes.currentPeriod.value ?? 0) / sessions.currentPeriod.value + : 0, + timeseries: crashes.currentPeriod.timeseries.map((bucket, i) => { + const sessionValue = sessions.currentPeriod.timeseries[i].y; + return { + x: bucket.x, + y: sessionValue ? (bucket.y ?? 0) / sessionValue : 0, + }; + }) as Timeseries[], + }, }; } @@ -107,6 +122,7 @@ export async function getMobileStatsPeriods({ : { sessions: { timeseries: [], value: null }, requests: { timeseries: [], value: null }, + crashRate: { timeseries: [], value: null }, }; const [currentPeriod, previousPeriod] = await Promise.all([ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 50e6763aad805..31789c2078294 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -8271,7 +8271,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "Le plus utilisé dans", "xpack.apm.mobile.location.metrics.launches": "La plupart des lancements", "xpack.apm.mobile.location.metrics.sessions": "La plupart des sessions", - "xpack.apm.mobile.metrics.crash.rate": "Taux de panne (pannes par minute)", "xpack.apm.mobile.metrics.http.requests": "Requêtes HTTP", "xpack.apm.mobile.metrics.load.time": "Temps de chargement de l'application le plus lent", "xpack.apm.mobile.metrics.sessions": "Sessions", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d2b3c60becf20..88b6cac53927d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8287,7 +8287,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "最も使用されている", "xpack.apm.mobile.location.metrics.launches": "最も多い起動", "xpack.apm.mobile.location.metrics.sessions": "最も多いセッション", - "xpack.apm.mobile.metrics.crash.rate": "クラッシュ率(毎分のクラッシュ数)", "xpack.apm.mobile.metrics.http.requests": "HTTPリクエスト", "xpack.apm.mobile.metrics.load.time": "最も遅いアプリ読み込み時間", "xpack.apm.mobile.metrics.sessions": "セッション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 21a7856a7baa3..1328b87d357fa 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8286,7 +8286,6 @@ "xpack.apm.mobile.location.metrics.http.requests.title": "最常用于", "xpack.apm.mobile.location.metrics.launches": "大多数启动", "xpack.apm.mobile.location.metrics.sessions": "大多数会话", - "xpack.apm.mobile.metrics.crash.rate": "崩溃速率(每分钟崩溃数)", "xpack.apm.mobile.metrics.http.requests": "HTTP 请求", "xpack.apm.mobile.metrics.load.time": "最慢应用加载时间", "xpack.apm.mobile.metrics.sessions": "会话", diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts index 447aebf727767..94193f2946ece 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_location_stats.spec.ts @@ -219,6 +219,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); }); }); @@ -253,6 +256,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { location } = response.currentPeriod.mostRequests; expect(location).to.be('China'); }); + + it('returns location for most crashes', () => { + const { location } = response.currentPeriod.mostCrashes; + expect(location).to.be('China'); + }); }); describe('when filters are applied', () => { @@ -265,6 +273,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(0); expect(response.currentPeriod.mostRequests.value).to.eql(0); + expect(response.currentPeriod.mostCrashes.value).to.eql(0); expect(response.currentPeriod.mostSessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -272,6 +281,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostRequests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.mostCrashes.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); it('returns the correct values when single filter is applied', async () => { @@ -283,6 +295,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); }); it('returns the correct values when multiple filters are applied', async () => { @@ -293,6 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.mostSessions.value).to.eql(3); expect(response.currentPeriod.mostRequests.value).to.eql(3); + expect(response.currentPeriod.mostCrashes.value).to.eql(3); }); }); }); diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts index 477d5456315cf..edc852d97ad2a 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts @@ -10,7 +10,7 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { sumBy } from 'lodash'; +import { sumBy, meanBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>; @@ -103,7 +103,7 @@ async function generateData({ return [ galaxy10 .transaction('Start View - View Appearing', 'Android Activity') - .errors(galaxy10.crash({ message: 'error' }).timestamp(timestamp)) + .errors(galaxy10.crash({ message: 'error C' }).timestamp(timestamp)) .timestamp(timestamp) .duration(500) .success() @@ -120,7 +120,11 @@ async function generateData({ ), huaweiP2 .transaction('Start View - View Appearing', 'huaweiP2 Activity') - .errors(huaweiP2.crash({ message: 'error' }).timestamp(timestamp)) + .errors( + huaweiP2.crash({ message: 'error A' }).timestamp(timestamp), + huaweiP2.crash({ message: 'error B' }).timestamp(timestamp), + huaweiP2.crash({ message: 'error D' }).timestamp(timestamp) + ) .timestamp(timestamp) .duration(20) .success(), @@ -211,6 +215,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { const timeseriesTotal = sumBy(timeseries, 'y'); expect(value).to.be(timeseriesTotal); }); + + it('returns same crashes', () => { + const { value, timeseries } = response.currentPeriod.crashRate; + const timeseriesMean = meanBy( + timeseries.filter((bucket) => bucket.y !== 0), + 'y' + ); + expect(value).to.be(timeseriesMean); + }); }); describe('when filters are applied', () => { @@ -223,6 +236,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(0); expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(0); expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -230,6 +244,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.requests.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql( + true + ); }); it('returns the correct values when single filter is applied', async () => { @@ -241,6 +258,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(0); + expect(response.currentPeriod.crashRate.value).to.eql(3); }); it('returns the correct values when multiple filters are applied', async () => { @@ -248,9 +266,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceName: 'synth-android', kuery: `service.version:"1.2" and service.environment: "production"`, }); - expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(3); + expect(response.currentPeriod.crashRate.value).to.eql(1); }); }); }); From 15745024d4085e55de93209924a773cf81c6fa77 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Mon, 18 Sep 2023 14:18:19 -0400 Subject: [PATCH 28/58] [Response Ops][Alerting] SLIs Phase 2 - Histogram for task schedule delay (#166122) Towards https://github.com/elastic/response-ops-team/issues/130 ## Summary This implements the second of 3 SLIs described in https://github.com/elastic/response-ops-team/issues/130 - the histogram for tracking task schedule delay in seconds. We bucket up to 30 minutes. ## To Verify Run Kibana and create some alerting rules. Navigate to https://localhost:5601/api/task_manager/metrics?reset=false and you should see that the new metric under `task_run.value.overall.delay` Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/metrics/create_aggregator.test.ts | 797 ++++++++++++++++-- .../server/metrics/metrics_stream.ts | 5 +- .../server/metrics/simple_histogram.test.ts | 17 + .../server/metrics/simple_histogram.ts | 18 + .../metrics/task_claim_metrics_aggregator.ts | 21 +- .../task_run_metrics_aggregator.test.ts | 58 +- .../metrics/task_run_metrics_aggregator.ts | 51 +- .../task_manager/server/task_events.ts | 3 +- .../server/task_running/task_runner.test.ts | 29 +- .../server/task_running/task_runner.ts | 18 +- .../test_suites/task_manager/metrics_route.ts | 21 +- 11 files changed, 923 insertions(+), 115 deletions(-) diff --git a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts index 6ff99d482581a..91aa56e3aa921 100644 --- a/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/create_aggregator.test.ts @@ -8,7 +8,7 @@ import sinon from 'sinon'; import { Subject, Observable } from 'rxjs'; import { take, bufferCount, skip } from 'rxjs/operators'; -import { isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; +import { isTaskManagerStatEvent, isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { AggregatedStat } from '../lib/runtime_statistics_aggregator'; import { taskPollingLifecycleMock } from '../polling_lifecycle.mock'; @@ -16,7 +16,11 @@ import { TaskManagerConfig } from '../config'; import { createAggregator } from './create_aggregator'; import { TaskClaimMetric, TaskClaimMetricsAggregator } from './task_claim_metrics_aggregator'; import { taskClaimFailureEvent, taskClaimSuccessEvent } from './task_claim_metrics_aggregator.test'; -import { getTaskRunFailedEvent, getTaskRunSuccessEvent } from './task_run_metrics_aggregator.test'; +import { + getTaskRunFailedEvent, + getTaskRunSuccessEvent, + getTaskManagerStatEvent, +} from './task_run_metrics_aggregator.test'; import { TaskRunMetric, TaskRunMetricsAggregator } from './task_run_metrics_aggregator'; import * as TaskClaimMetricsAggregatorModule from './task_claim_metrics_aggregator'; import { metricsAggregatorMock } from './metrics_aggregator.mock'; @@ -370,17 +374,27 @@ describe('createAggregator', () => { }); describe('with TaskRunMetricsAggregator', () => { - test('returns a cumulative count of successful task runs and total task runs, broken down by type', async () => { + test('returns a cumulative count of successful task runs, on time task runs and total task runs, broken down by type, along with histogram of run delays', async () => { const taskRunEvents = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:.index-threshold'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); @@ -393,7 +407,8 @@ describe('createAggregator', () => { taskPollingLifecycle, config, resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + taskEventFilter: (taskEvent: TaskLifecycleEvent) => + isTaskRunEvent(taskEvent) || isTaskManagerStatEvent(taskEvent), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -410,17 +425,53 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, not_timed_out: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + }, + }); + expect(metrics[1]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); - expect(metrics[1]).toEqual({ + expect(metrics[2]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[3]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, @@ -428,10 +479,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[2]).toEqual({ + expect(metrics[4]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 3, total: 3 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[5]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -439,10 +511,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[3]).toEqual({ + expect(metrics[6]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 4, total: 4 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -451,10 +544,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[4]).toEqual({ + expect(metrics[8]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[9]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 5, total: 5 }, + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 3, total: 3 }, 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, @@ -463,10 +578,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[5]).toEqual({ + expect(metrics[10]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[11]).toEqual({ key: 'task_run', value: { - overall: { success: 5, not_timed_out: 6, total: 6 }, + overall: { + success: 5, + not_timed_out: 6, + total: 6, + delay: { counts: [4, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 3, not_timed_out: 4, total: 4 }, 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, @@ -476,10 +613,33 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[6]).toEqual({ + expect(metrics[12]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 5, + not_timed_out: 6, + total: 6, + delay: { counts: [4, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[13]).toEqual({ key: 'task_run', value: { - overall: { success: 6, not_timed_out: 7, total: 7 }, + overall: { + success: 6, + not_timed_out: 7, + total: 7, + delay: { counts: [4, 2, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 4, not_timed_out: 5, total: 5 }, 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, @@ -489,10 +649,33 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[7]).toEqual({ + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 6, + not_timed_out: 7, + total: 7, + delay: { counts: [5, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 4, not_timed_out: 5, total: 5 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[15]).toEqual({ key: 'task_run', value: { - overall: { success: 6, not_timed_out: 8, total: 8 }, + overall: { + success: 6, + not_timed_out: 8, + total: 8, + delay: { counts: [5, 2, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 4, not_timed_out: 6, total: 6 }, 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, @@ -502,10 +685,33 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[8]).toEqual({ + expect(metrics[16]).toEqual({ key: 'task_run', value: { - overall: { success: 7, not_timed_out: 9, total: 9 }, + overall: { + success: 6, + not_timed_out: 8, + total: 8, + delay: { counts: [6, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 4, not_timed_out: 6, total: 6 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 3, not_timed_out: 5, total: 5 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[17]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 7, + not_timed_out: 9, + total: 9, + delay: { counts: [6, 2, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 5, not_timed_out: 7, total: 7 }, 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, @@ -515,10 +721,33 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[9]).toEqual({ + expect(metrics[18]).toEqual({ key: 'task_run', value: { - overall: { success: 7, not_timed_out: 10, total: 10 }, + overall: { + success: 7, + not_timed_out: 9, + total: 9, + delay: { counts: [7, 2, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 5, not_timed_out: 7, total: 7 }, + 'alerting:__index-threshold': { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 4, not_timed_out: 6, total: 6 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[19]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 7, + not_timed_out: 10, + total: 10, + delay: { counts: [7, 2, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { actions: { success: 0, not_timed_out: 1, total: 1 }, alerting: { success: 5, not_timed_out: 7, total: 7 }, @@ -542,18 +771,28 @@ describe('createAggregator', () => { test('resets count when resetMetric$ event is received', async () => { const resetMetrics$ = new Subject(); const taskRunEvents1 = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), ]; const taskRunEvents2 = [ + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); @@ -566,7 +805,8 @@ describe('createAggregator', () => { taskPollingLifecycle, config, resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + taskEventFilter: (taskEvent: TaskLifecycleEvent) => + isTaskRunEvent(taskEvent) || isTaskManagerStatEvent(taskEvent), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -583,17 +823,53 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, not_timed_out: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + }, + }); + expect(metrics[1]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); - expect(metrics[1]).toEqual({ + expect(metrics[2]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[3]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, @@ -601,10 +877,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[2]).toEqual({ + expect(metrics[4]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[5]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 3, total: 3 }, + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -612,10 +909,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[3]).toEqual({ + expect(metrics[6]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 4, total: 4 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -624,10 +942,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[4]).toEqual({ + expect(metrics[8]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 5, total: 5 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[9]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 3, total: 3 }, 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, @@ -637,10 +977,32 @@ describe('createAggregator', () => { }, }); // reset event should have been received here - expect(metrics[5]).toEqual({ + expect(metrics[10]).toEqual({ key: 'task_run', value: { - overall: { success: 1, not_timed_out: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:example': { success: 0, not_timed_out: 0, total: 0 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[11]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, @@ -649,10 +1011,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[6]).toEqual({ + expect(metrics[12]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[13]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -661,10 +1045,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[7]).toEqual({ + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[15]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 3, total: 3 }, + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 3, total: 3 }, 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, @@ -673,10 +1079,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[8]).toEqual({ + expect(metrics[16]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 4, total: 4 }, + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[17]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 3, not_timed_out: 4, total: 4 }, 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, @@ -685,10 +1113,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[9]).toEqual({ + expect(metrics[18]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [4, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[19]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 5, total: 5 }, + overall: { + success: 3, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1], values: [10, 20] }, + }, by_type: { actions: { success: 0, not_timed_out: 1, total: 1 }, alerting: { success: 3, not_timed_out: 4, total: 4 }, @@ -716,18 +1166,28 @@ describe('createAggregator', () => { const clock = sinon.useFakeTimers(); clock.tick(0); const taskRunEvents1 = [ + getTaskManagerStatEvent(3.234), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(10.45), getTaskRunSuccessEvent('telemetry'), + getTaskManagerStatEvent(3.454), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(35.45), getTaskRunSuccessEvent('report'), + getTaskManagerStatEvent(8.85673), getTaskRunFailedEvent('alerting:example'), ]; const taskRunEvents2 = [ + getTaskManagerStatEvent(4.5745), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(11.564), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.78), getTaskRunFailedEvent('alerting:example'), + getTaskManagerStatEvent(3.7863), getTaskRunSuccessEvent('alerting:example'), + getTaskManagerStatEvent(3.245), getTaskRunFailedEvent('actions:webhook'), ]; const events$ = new Subject(); @@ -743,7 +1203,8 @@ describe('createAggregator', () => { metrics_reset_interval: 10, }, resetMetrics$: new Subject(), - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + taskEventFilter: (taskEvent: TaskLifecycleEvent) => + isTaskRunEvent(taskEvent) || isTaskManagerStatEvent(taskEvent), metricsAggregator: new TaskRunMetricsAggregator(), }); @@ -760,17 +1221,53 @@ describe('createAggregator', () => { expect(metrics[0]).toEqual({ key: 'task_run', value: { - overall: { success: 1, not_timed_out: 1, total: 1 }, + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + }, + }); + expect(metrics[1]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[2]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, }, }, }); - expect(metrics[1]).toEqual({ + expect(metrics[3]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, @@ -778,10 +1275,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[2]).toEqual({ + expect(metrics[4]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 3, total: 3 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[5]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -789,10 +1307,31 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[3]).toEqual({ + expect(metrics[6]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[7]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 4, total: 4 }, + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [2, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -801,10 +1340,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[4]).toEqual({ + expect(metrics[8]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 4, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 1, not_timed_out: 1, total: 1 }, + telemetry: { success: 1, not_timed_out: 1, total: 1 }, + }, + }, + }); + expect(metrics[9]).toEqual({ key: 'task_run', value: { - overall: { success: 4, not_timed_out: 5, total: 5 }, + overall: { + success: 4, + not_timed_out: 5, + total: 5, + delay: { counts: [3, 1, 0, 1], values: [10, 20, 30, 40] }, + }, by_type: { alerting: { success: 2, not_timed_out: 3, total: 3 }, 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, @@ -814,10 +1375,32 @@ describe('createAggregator', () => { }, }); // reset event should have been received here - expect(metrics[5]).toEqual({ + expect(metrics[10]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 0, + not_timed_out: 0, + total: 0, + delay: { counts: [1], values: [10] }, + }, + by_type: { + alerting: { success: 0, not_timed_out: 0, total: 0 }, + 'alerting:example': { success: 0, not_timed_out: 0, total: 0 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[11]).toEqual({ key: 'task_run', value: { - overall: { success: 1, not_timed_out: 1, total: 1 }, + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1], values: [10] }, + }, by_type: { alerting: { success: 1, not_timed_out: 1, total: 1 }, 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, @@ -826,10 +1409,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[6]).toEqual({ + expect(metrics[12]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 1, + not_timed_out: 1, + total: 1, + delay: { counts: [1, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 1, not_timed_out: 1, total: 1 }, + 'alerting:example': { success: 1, not_timed_out: 1, total: 1 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[13]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [1, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 2, total: 2 }, 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, @@ -838,10 +1443,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[7]).toEqual({ + expect(metrics[14]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 2, + total: 2, + delay: { counts: [2, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 2, total: 2 }, + 'alerting:example': { success: 2, not_timed_out: 2, total: 2 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[15]).toEqual({ key: 'task_run', value: { - overall: { success: 2, not_timed_out: 3, total: 3 }, + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [2, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 2, not_timed_out: 3, total: 3 }, 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, @@ -850,10 +1477,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[8]).toEqual({ + expect(metrics[16]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 2, + not_timed_out: 3, + total: 3, + delay: { counts: [3, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 2, not_timed_out: 3, total: 3 }, + 'alerting:example': { success: 2, not_timed_out: 3, total: 3 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[17]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 4, total: 4 }, + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [3, 1], values: [10, 20] }, + }, by_type: { alerting: { success: 3, not_timed_out: 4, total: 4 }, 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, @@ -862,10 +1511,32 @@ describe('createAggregator', () => { }, }, }); - expect(metrics[9]).toEqual({ + expect(metrics[18]).toEqual({ key: 'task_run', value: { - overall: { success: 3, not_timed_out: 5, total: 5 }, + overall: { + success: 3, + not_timed_out: 4, + total: 4, + delay: { counts: [4, 1], values: [10, 20] }, + }, + by_type: { + alerting: { success: 3, not_timed_out: 4, total: 4 }, + 'alerting:example': { success: 3, not_timed_out: 4, total: 4 }, + report: { success: 0, not_timed_out: 0, total: 0 }, + telemetry: { success: 0, not_timed_out: 0, total: 0 }, + }, + }, + }); + expect(metrics[19]).toEqual({ + key: 'task_run', + value: { + overall: { + success: 3, + not_timed_out: 5, + total: 5, + delay: { counts: [4, 1], values: [10, 20] }, + }, by_type: { actions: { success: 0, not_timed_out: 1, total: 1 }, alerting: { success: 3, not_timed_out: 4, total: 4 }, diff --git a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts index 29558308c5196..4b4ce6075d6c6 100644 --- a/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts +++ b/x-pack/plugins/task_manager/server/metrics/metrics_stream.ts @@ -11,7 +11,7 @@ import { set } from '@kbn/safer-lodash-set'; import { TaskLifecycleEvent, TaskPollingLifecycle } from '../polling_lifecycle'; import { TaskManagerConfig } from '../config'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; +import { isTaskManagerStatEvent, isTaskPollingCycleEvent, isTaskRunEvent } from '../task_events'; import { TaskClaimMetric, TaskClaimMetricsAggregator } from './task_claim_metrics_aggregator'; import { createAggregator } from './create_aggregator'; import { TaskRunMetric, TaskRunMetricsAggregator } from './task_run_metrics_aggregator'; @@ -54,7 +54,8 @@ export function createMetricsAggregators({ taskPollingLifecycle, config, resetMetrics$, - taskEventFilter: (taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent), + taskEventFilter: (taskEvent: TaskLifecycleEvent) => + isTaskRunEvent(taskEvent) || isTaskManagerStatEvent(taskEvent), metricsAggregator: new TaskRunMetricsAggregator(), }) ); diff --git a/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts b/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts index 30b5d0bd6b21a..24ae09c743501 100644 --- a/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/simple_histogram.test.ts @@ -176,4 +176,21 @@ describe('SimpleHistogram', () => { expect(histogram.get(true)).toEqual([]); }); + + test('should correctly serialize histogram data', () => { + const histogram = new SimpleHistogram(100, 10); + histogram.record(23); + histogram.record(34); + histogram.record(21); + histogram.record(56); + histogram.record(78); + histogram.record(33); + histogram.record(99); + histogram.record(1); + histogram.record(2); + expect(histogram.serialize()).toEqual({ + counts: [2, 0, 2, 2, 0, 1, 0, 1, 0, 1], + values: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + }); + }); }); diff --git a/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts b/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts index 3b2cb89a7f5dd..4b888c8cc4b3d 100644 --- a/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts +++ b/x-pack/plugins/task_manager/server/metrics/simple_histogram.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { JsonObject } from '@kbn/utility-types'; import { last } from 'lodash'; interface Bucket { @@ -13,6 +14,11 @@ interface Bucket { count: number; } +export interface SerializedHistogram extends JsonObject { + counts: number[]; + values: number[]; +} + export class SimpleHistogram { private maxValue: number; private bucketSize: number; @@ -68,6 +74,18 @@ export class SimpleHistogram { })); } + public serialize(): SerializedHistogram { + const counts: number[] = []; + const values: number[] = []; + + for (const { count, value } of this.get(true)) { + counts.push(count); + values.push(value); + } + + return { counts, values }; + } + private initializeBuckets() { let i = 0; while (i < this.maxValue) { diff --git a/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts index a10ddf89c77b0..60bb2401f420d 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_claim_metrics_aggregator.ts @@ -9,7 +9,7 @@ import { JsonObject } from '@kbn/utility-types'; import { isOk } from '../lib/result_type'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { TaskRun } from '../task_events'; -import { SimpleHistogram } from './simple_histogram'; +import { SerializedHistogram, SimpleHistogram } from './simple_histogram'; import { ITaskMetricsAggregator } from './types'; import { MetricCounterService } from './counter/metric_counter_service'; @@ -26,10 +26,7 @@ interface TaskClaimCounts extends JsonObject { } export type TaskClaimMetric = TaskClaimCounts & { - duration: { - counts: number[]; - values: number[]; - }; + duration: SerializedHistogram; }; export class TaskClaimMetricsAggregator implements ITaskMetricsAggregator { @@ -47,7 +44,7 @@ export class TaskClaimMetricsAggregator implements ITaskMetricsAggregator { + return asTaskManagerStatEvent(id, asOk(value)); +}; + describe('TaskRunMetricsAggregator', () => { let taskRunMetricsAggregator: TaskRunMetricsAggregator; beforeEach(() => { @@ -77,13 +86,14 @@ describe('TaskRunMetricsAggregator', () => { test('should correctly initialize', () => { expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, not_timed_out: 0, total: 0 }, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, }); }); test('should correctly return initialMetrics', () => { expect(taskRunMetricsAggregator.initialMetric()).toEqual({ - overall: { success: 0, not_timed_out: 0, total: 0 }, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, + by_type: {}, }); }); @@ -91,18 +101,34 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 2, not_timed_out: 2, total: 2 }, + overall: { success: 2, not_timed_out: 2, total: 2, delay: { counts: [], values: [] } }, by_type: { telemetry: { success: 2, not_timed_out: 2, total: 2 }, }, }); }); + test('should correctly process task manager runDelay stat', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(3.343)); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [1], values: [10] } }, + }); + }); + + test('should ignore task manager stats that are not runDelays', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent( + getTaskManagerStatEvent(3.343, 'pollingDelay') + ); + expect(taskRunMetricsAggregator.collect()).toEqual({ + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, + }); + }); + test('should correctly process task run success event where task run has timed out', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry', true)); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry', true)); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 2, not_timed_out: 0, total: 2 }, + overall: { success: 2, not_timed_out: 0, total: 2, delay: { counts: [], values: [] } }, by_type: { telemetry: { success: 2, not_timed_out: 0, total: 2 }, }, @@ -113,7 +139,7 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, not_timed_out: 2, total: 2 }, + overall: { success: 0, not_timed_out: 2, total: 2, delay: { counts: [], values: [] } }, by_type: { telemetry: { success: 0, not_timed_out: 2, total: 2 }, }, @@ -124,7 +150,7 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry', true)); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry', true)); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, not_timed_out: 0, total: 2 }, + overall: { success: 0, not_timed_out: 0, total: 2, delay: { counts: [], values: [] } }, by_type: { telemetry: { success: 0, not_timed_out: 0, total: 2 }, }, @@ -137,7 +163,7 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report', true)); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunFailedEvent('telemetry')); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 3, not_timed_out: 3, total: 4 }, + overall: { success: 3, not_timed_out: 3, total: 4, delay: { counts: [], values: [] } }, by_type: { report: { success: 2, not_timed_out: 1, total: 2 }, telemetry: { success: 1, not_timed_out: 2, total: 2 }, @@ -167,7 +193,7 @@ describe('TaskRunMetricsAggregator', () => { getTaskRunSuccessEvent('alerting:.index-threshold', true) ); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 11, not_timed_out: 12, total: 14 }, + overall: { success: 11, not_timed_out: 12, total: 14, delay: { counts: [], values: [] } }, by_type: { actions: { success: 3, not_timed_out: 3, total: 3 }, 'actions:__email': { success: 1, not_timed_out: 1, total: 1 }, @@ -182,6 +208,11 @@ describe('TaskRunMetricsAggregator', () => { }); test('should correctly reset counter', () => { + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(3.343)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(25.45)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(6.4478)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskManagerStatEvent(9.241)); + taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('telemetry')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); taskRunMetricsAggregator.processTaskLifecycleEvent(getTaskRunSuccessEvent('report')); @@ -203,7 +234,12 @@ describe('TaskRunMetricsAggregator', () => { getTaskRunSuccessEvent('alerting:.index-threshold') ); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 11, not_timed_out: 12, total: 14 }, + overall: { + success: 11, + not_timed_out: 12, + total: 14, + delay: { counts: [3, 0, 1], values: [10, 20, 30] }, + }, by_type: { actions: { success: 3, not_timed_out: 3, total: 3 }, 'actions:__email': { success: 1, not_timed_out: 1, total: 1 }, @@ -218,7 +254,7 @@ describe('TaskRunMetricsAggregator', () => { taskRunMetricsAggregator.reset(); expect(taskRunMetricsAggregator.collect()).toEqual({ - overall: { success: 0, not_timed_out: 0, total: 0 }, + overall: { success: 0, not_timed_out: 0, total: 0, delay: { counts: [], values: [] } }, by_type: { actions: { success: 0, not_timed_out: 0, total: 0 }, 'actions:__email': { success: 0, not_timed_out: 0, total: 0 }, diff --git a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts index 685c5a8cc8838..ced75af5718eb 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_run_metrics_aggregator.ts @@ -6,14 +6,26 @@ */ import { JsonObject } from '@kbn/utility-types'; -import { isOk, unwrap } from '../lib/result_type'; +import { merge } from 'lodash'; +import { isOk, Ok, unwrap } from '../lib/result_type'; import { TaskLifecycleEvent } from '../polling_lifecycle'; -import { ErroredTask, RanTask, TaskRun } from '../task_events'; +import { + ErroredTask, + RanTask, + TaskRun, + isTaskManagerStatEvent, + isTaskRunEvent, + TaskManagerStat, +} from '../task_events'; import { MetricCounterService } from './counter/metric_counter_service'; +import { SerializedHistogram, SimpleHistogram } from './simple_histogram'; import { ITaskMetricsAggregator } from './types'; const taskTypeGrouping = new Set(['alerting:', 'actions:']); +const HDR_HISTOGRAM_MAX = 1800; // 30 minutes +const HDR_HISTOGRAM_BUCKET_SIZE = 10; // 10 seconds + enum TaskRunKeys { SUCCESS = 'success', NOT_TIMED_OUT = 'not_timed_out', @@ -31,33 +43,53 @@ interface TaskRunCounts extends JsonObject { [TaskRunKeys.TOTAL]: number; } -export interface TaskRunMetric extends JsonObject { +export interface TaskRunMetrics extends JsonObject { [TaskRunMetricKeys.OVERALL]: TaskRunCounts; [TaskRunMetricKeys.BY_TYPE]: { [key: string]: TaskRunCounts; }; } +export interface TaskRunMetric extends JsonObject { + overall: TaskRunMetrics['overall'] & { + delay: SerializedHistogram; + }; + by_type: TaskRunMetrics['by_type']; +} + export class TaskRunMetricsAggregator implements ITaskMetricsAggregator { private counter: MetricCounterService = new MetricCounterService( Object.values(TaskRunKeys), TaskRunMetricKeys.OVERALL ); + private delayHistogram = new SimpleHistogram(HDR_HISTOGRAM_MAX, HDR_HISTOGRAM_BUCKET_SIZE); public initialMetric(): TaskRunMetric { - return this.counter.initialMetrics(); + return merge(this.counter.initialMetrics(), { + by_type: {}, + overall: { delay: { counts: [], values: [] } }, + }); } public collect(): TaskRunMetric { - return this.counter.collect(); + return merge(this.counter.collect(), { overall: { delay: this.delayHistogram.serialize() } }); } public reset() { this.counter.reset(); + this.delayHistogram.reset(); } public processTaskLifecycleEvent(taskEvent: TaskLifecycleEvent) { - const { task, isExpired }: RanTask | ErroredTask = unwrap((taskEvent as TaskRun).event); + if (isTaskRunEvent(taskEvent)) { + this.processTaskRunEvent(taskEvent); + } else if (isTaskManagerStatEvent(taskEvent)) { + this.processTaskManagerStatEvent(taskEvent); + } + } + + private processTaskRunEvent(taskEvent: TaskRun) { + const { task, isExpired }: RanTask | ErroredTask = unwrap(taskEvent.event); const success = isOk((taskEvent as TaskRun).event); const taskType = task.taskType.replaceAll('.', '__'); const taskTypeGroup = this.getTaskTypeGroup(taskType); @@ -76,6 +108,13 @@ export class TaskRunMetricsAggregator implements ITaskMetricsAggregator).value); + this.delayHistogram.record(delayInSec); + } + } + private incrementCounters(key: TaskRunKeys, taskType: string, group?: string) { this.counter.increment(key, TaskRunMetricKeys.OVERALL); this.counter.increment(key, `${TaskRunMetricKeys.BY_TYPE}.${taskType}`); diff --git a/x-pack/plugins/task_manager/server/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts index 68eaec9d9b28e..63aacd125b1b0 100644 --- a/x-pack/plugins/task_manager/server/task_events.ts +++ b/x-pack/plugins/task_manager/server/task_events.ts @@ -89,7 +89,8 @@ export type TaskManagerStats = | 'claimDuration' | 'queuedEphemeralTasks' | 'ephemeralTaskDelay' - | 'workerUtilization'; + | 'workerUtilization' + | 'runDelay'; export type TaskManagerStat = TaskEvent; export type OkResultOf = EventType extends TaskEvent diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index 6676c17ccc630..1ae176d6993b3 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -16,6 +16,7 @@ import { asTaskMarkRunningEvent, TaskRun, TaskPersistence, + asTaskManagerStatEvent, } from '../task_events'; import { ConcreteTaskInstance, TaskStatus } from '../task'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; @@ -927,7 +928,10 @@ describe('TaskManagerRunner', () => { ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('tasks that return runAt override the schedule', async () => { @@ -1300,6 +1304,9 @@ describe('TaskManagerRunner', () => { ) ) ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); }); test('emits TaskEvent when a recurring task is run successfully', async () => { @@ -1381,6 +1388,9 @@ describe('TaskManagerRunner', () => { ) ) ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); }); test('emits TaskEvent when a recurring task returns a success result with hasError=true', async () => { @@ -1502,7 +1512,7 @@ describe('TaskManagerRunner', () => { ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('emits TaskEvent when a task run throws an error and has timed out', async () => { @@ -1544,7 +1554,10 @@ describe('TaskManagerRunner', () => { ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('emits TaskEvent when a task run returns an error', async () => { @@ -1586,7 +1599,10 @@ describe('TaskManagerRunner', () => { ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); test('emits TaskEvent when a task returns an error and is marked as failed', async () => { @@ -1639,7 +1655,10 @@ describe('TaskManagerRunner', () => { ) ) ); - expect(onTaskEvent).toHaveBeenCalledTimes(1); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); }); }); diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 1742d88bc8f07..63f520ade1031 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -33,11 +33,13 @@ import { import { asTaskMarkRunningEvent, asTaskRunEvent, + asTaskManagerStatEvent, startTaskTimerWithEventLoopMonitoring, TaskMarkRunning, TaskPersistence, TaskRun, TaskTiming, + TaskManagerStat, } from '../task_events'; import { intervalFromDate, maxIntervalFromDate } from '../lib/intervals'; import { @@ -101,7 +103,7 @@ type Opts = { definitions: TaskTypeDictionary; instance: ConcreteTaskInstance; store: Updatable; - onTaskEvent?: (event: TaskRun | TaskMarkRunning) => void; + onTaskEvent?: (event: TaskRun | TaskMarkRunning | TaskManagerStat) => void; defaultMaxAttempts: number; executionContext: ExecutionContextStart; usageCounter?: UsageCounter; @@ -149,7 +151,7 @@ export class TaskManagerRunner implements TaskRunner { private bufferedTaskStore: Updatable; private beforeRun: Middleware['beforeRun']; private beforeMarkRunning: Middleware['beforeMarkRunning']; - private onTaskEvent: (event: TaskRun | TaskMarkRunning) => void; + private onTaskEvent: (event: TaskRun | TaskMarkRunning | TaskManagerStat) => void; private defaultMaxAttempts: number; private uuid: string; private readonly executionContext: ExecutionContextStart; @@ -310,6 +312,13 @@ export class TaskManagerRunner implements TaskRunner { const stopTaskTimer = startTaskTimerWithEventLoopMonitoring(this.eventLoopDelayConfig); + this.onTaskEvent( + asTaskManagerStatEvent( + 'runDelay', + asOk(getTaskDelayInSeconds(this.instance.task.scheduledAt)) + ) + ); + try { this.task = this.definition.createTaskRunner(modifiedContext); @@ -893,3 +902,8 @@ export function calculateDelay(attempts: number) { return defaultBackoffPerFailure * Math.pow(2, attempts - 2); } } + +export function getTaskDelayInSeconds(scheduledAt: Date) { + const now = new Date(); + return (now.valueOf() - scheduledAt.valueOf()) / 1000; +} diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts index 7a0507959c117..faf3f63712ce4 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/metrics_route.ts @@ -207,13 +207,20 @@ export default function ({ getService }: FtrProviderContext) { .send({ task: { id: ruleId } }) .expect(200); - await getMetrics( - false, - (metrics) => - metrics?.metrics?.task_run?.value.by_type.alerting?.total === i + 2 && - metrics?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === i + 2 && - metrics?.metrics?.task_run?.value.by_type.alerting?.success === i + 2 - ); + const metrics = ( + await getMetrics( + false, + (m) => + m?.metrics?.task_run?.value.by_type.alerting?.total === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.success === i + 2 + ) + ).metrics; + + // check that delay histogram exists + expect(metrics?.task_run?.value?.overall?.delay).not.to.be(null); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.counts)).to.be(true); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.values)).to.be(true); } // counter should reset on its own From 543dbcbd48a14f2549e7ec7c8947fe5ce495daa4 Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Mon, 18 Sep 2023 14:32:47 -0400 Subject: [PATCH 29/58] [DOCS] Add security update to 8.10.0 release notes (#166656) --- docs/CHANGELOG.asciidoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 297a347e721ba..253770bb7332d 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -76,6 +76,22 @@ IMPORTANT: {kib} 8.10.0 has been withdrawn. For information about the {kib} 8.10.0 release, review the following information. +[float] +[[security-updates-v8.10.0]] +=== Security Updates + +* An issue was discovered by Elastic whereby sensitive information is recorded +in {kib} logs in the event of an error. The issue impacts only {kib} version +8.10.0 when logging in the JSON layout or when the pattern layout is configured +to log the `%meta` pattern. ++ +The issue is resolved in {kib} 8.10.1. Version 8.10.0 has been removed from our +download sites. ++ +For more information, see our related +https://discuss.elastic.co/t/kibana-8-10-1-security-update/343287[security +announcement]. + [float] [[breaking-changes-8.10.0]] === Breaking changes From 6bf6d7650b51beacdcb777a5fc681b93a8749f2f Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Mon, 18 Sep 2023 15:34:56 -0400 Subject: [PATCH 30/58] Unskips serverless user profile API tests (#165516) Closes #165769 Closes #165391 Unskips the serverless user profile API integration tests (still skipped for MKI only). Flaky Runner: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3166 --- .../test_suites/common/security/user_profiles.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/security/user_profiles.ts b/x-pack/test_serverless/api_integration/test_suites/common/security/user_profiles.ts index 64650c5f47548..83ef0c4335679 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/security/user_profiles.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/security/user_profiles.ts @@ -16,8 +16,7 @@ export default function ({ getService }: FtrProviderContext) { describe('security/user_profiles', function () { describe('route access', () => { - // FLAKY: https://github.com/elastic/kibana/issues/165391 - describe.skip('internal', () => { + describe('internal', () => { // When we run tests on MKI, SAML realm is configured differently, and we cannot handcraft SAML responses to // log in as SAML users. this.tags(['skipMKI']); From a97bfb32009cba9f8a40666977fc2a60ec53645f Mon Sep 17 00:00:00 2001 From: Lola Date: Mon, 18 Sep 2023 16:44:12 -0400 Subject: [PATCH 31/58] [Cloud Security]add cnvm dashboard link to package assets page (#166474) ## Summary [Quick wins](https://github.com/elastic/security-team/issues/7436) Add dashboard link panel to assets page. https://github.com/elastic/kibana/assets/17135495/ea1f5044-19aa-42c0-87da-516ed7bdad3c --- .../fleet_extensions/custom_assets_extension.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx index 0536c69a94701..774acadbd03ba 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/custom_assets_extension.tsx @@ -23,6 +23,16 @@ export const CspCustomAssetsExtension = () => { .integration; const viewsCNVM: CustomAssetsAccordionProps['views'] = [ + { + name: cloudPosturePages.vulnerability_dashboard.name, + url: application.getUrlForApp(SECURITY_APP_NAME, { + path: cloudPosturePages.vulnerability_dashboard.path, + }), + description: i18n.translate( + 'xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityDashboardViewLabel', + { defaultMessage: 'View CNVM Dashboard' } + ), + }, { name: cloudPosturePages.findings.name, url: application.getUrlForApp(SECURITY_APP_NAME, { From c486443e7665684f744f25804d079bbfa2f3f1c2 Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:05:38 +0200 Subject: [PATCH 32/58] Elasticsearch Query rule can group by multi-terms (#166146) Resolves: #163829 This PR allows multiple group-by terms to be selected in Elasticsearch query rule. Screenshot 2023-09-11 at 22 53 34 Screenshot 2023-09-11 at 22 53 53 --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ying Mao --- .../plugins/stack_alerts/common/constants.ts | 2 + .../public/rule_types/es_query/constants.ts | 1 + .../expression/es_query_expression.tsx | 1 + .../search_source_expression_form.tsx | 16 +- .../rule_common_expressions.test.tsx | 27 ++ .../rule_common_expressions.tsx | 3 + .../public/rule_types/es_query/types.ts | 2 +- .../rule_types/es_query/validation.test.ts | 40 +++ .../public/rule_types/es_query/validation.ts | 19 +- .../public/rule_types/threshold/types.ts | 2 +- .../es_query/rule_type_params.test.ts | 44 ++- .../rule_types/es_query/rule_type_params.ts | 8 +- x-pack/plugins/triggers_actions_ui/README.md | 10 +- .../common/data/lib/build_agg.test.ts | 61 ++++ .../common/data/lib/build_agg.ts | 22 +- .../expression_items/group_by_over.test.tsx | 304 +++++++++++------- .../common/expression_items/group_by_over.tsx | 123 ++++--- .../builtin_alert_types/es_query/rule.ts | 71 +++- 18 files changed, 560 insertions(+), 196 deletions(-) diff --git a/x-pack/plugins/stack_alerts/common/constants.ts b/x-pack/plugins/stack_alerts/common/constants.ts index cac00873face2..e846b249081e0 100644 --- a/x-pack/plugins/stack_alerts/common/constants.ts +++ b/x-pack/plugins/stack_alerts/common/constants.ts @@ -6,3 +6,5 @@ */ export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; + +export const MAX_SELECTABLE_GROUP_BY_TERMS = 4; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts index 4cce902449d18..f99b97634fc5a 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts @@ -22,6 +22,7 @@ export const DEFAULT_VALUES = { TERM_SIZE: 5, GROUP_BY: 'all', EXCLUDE_PREVIOUS_HITS: true, + CAN_SELECT_MULTI_TERMS: true, }; export const COMMON_EXPRESSION_ERRORS = { diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx index 2119879c26b39..7a9bb590af73b 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx @@ -351,6 +351,7 @@ export const EsQueryExpression: React.FC< (exclude) => setParam('excludeHitsFromPreviousRun', exclude), [setParam] )} + canSelectMultiTerms={DEFAULT_VALUES.CAN_SELECT_MULTI_TERMS} /> diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx index 1cc45801ffe65..b6c4cdd72bf62 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx @@ -14,12 +14,8 @@ import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import type { SearchBarProps } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { - mapAndFlattenFilters, - getTime, - type SavedQuery, - type ISearchSource, -} from '@kbn/data-plugin/public'; +import { mapAndFlattenFilters, getTime } from '@kbn/data-plugin/public'; +import type { SavedQuery, ISearchSource } from '@kbn/data-plugin/public'; import { BUCKET_SELECTOR_FIELD, buildAggregation, @@ -51,7 +47,9 @@ interface LocalState extends CommonRuleParams { interface LocalStateAction { type: SearchSourceParamsAction['type'] | keyof CommonRuleParams; - payload: SearchSourceParamsAction['payload'] | (number[] | number | string | boolean | undefined); + payload: + | SearchSourceParamsAction['payload'] + | (number[] | number | string | string[] | boolean | undefined); } type LocalStateReducer = (prevState: LocalState, action: LocalStateAction) => LocalState; @@ -201,7 +199,8 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp ); const onChangeSelectedTermField = useCallback( - (selectedTermField?: string) => dispatch({ type: 'termField', payload: selectedTermField }), + (selectedTermField?: string | string[]) => + dispatch({ type: 'termField', payload: selectedTermField }), [] ); @@ -372,6 +371,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp onCopyQuery={onCopyQuery} excludeHitsFromPreviousRun={ruleConfiguration.excludeHitsFromPreviousRun} onChangeExcludeHitsFromPreviousRun={onChangeExcludeHitsFromPreviousRun} + canSelectMultiTerms={DEFAULT_VALUES.CAN_SELECT_MULTI_TERMS} /> diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx index 119f0e02cdca2..267b289cf5ca8 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.test.tsx @@ -217,6 +217,33 @@ describe('RuleCommonExpressions', () => { ); }); + test(`should use multiple group by terms`, async () => { + const aggType = 'avg'; + const thresholdComparator = 'between'; + const timeWindowSize = 987; + const timeWindowUnit = 's'; + const threshold = [3, 1003]; + const groupBy = 'top'; + const termSize = '27'; + const termField = ['term', 'term2']; + + const wrapper = await setup({ + ruleParams: getCommonParams({ + aggType, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + termSize, + termField, + groupBy, + threshold, + }), + }); + expect(wrapper.find('button[data-test-subj="groupByExpression"]').text()).toEqual( + `grouped over ${groupBy} ${termSize} 'term,term2'` + ); + }); + test(`should disable excludeHitsFromPreviousRuns when groupBy is not all`, async () => { const aggType = 'avg'; const thresholdComparator = 'between'; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx index d74fce210354c..cee08144f307e 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/rule_common_expressions/rule_common_expressions.tsx @@ -52,6 +52,7 @@ export interface RuleCommonExpressionsProps extends CommonRuleParams { onTestFetch: TestQueryRowProps['fetch']; onCopyQuery?: TestQueryRowProps['copyQuery']; onChangeExcludeHitsFromPreviousRun: (exclude: boolean) => void; + canSelectMultiTerms?: boolean; } export const RuleCommonExpressions: React.FC = ({ @@ -82,6 +83,7 @@ export const RuleCommonExpressions: React.FC = ({ onCopyQuery, excludeHitsFromPreviousRun, onChangeExcludeHitsFromPreviousRun, + canSelectMultiTerms, }) => { const [isExcludeHitsDisabled, setIsExcludeHitsDisabled] = useState(false); @@ -127,6 +129,7 @@ export const RuleCommonExpressions: React.FC = ({ errors={errors} fields={esFields} display="fullWidth" + canSelectMultiTerms={canSelectMultiTerms} onChangeSelectedGroupBy={onChangeSelectedGroupBy} onChangeSelectedTermField={onChangeSelectedTermField} onChangeSelectedTermSize={onChangeSelectedTermSize} diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts index c7a298ebce22a..93b184f855232 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts @@ -27,7 +27,7 @@ export interface CommonRuleParams { aggField?: string; groupBy?: string; termSize?: number; - termField?: string; + termField?: string | string[]; excludeHitsFromPreviousRun: boolean; } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts index 90df11eb0c557..54363fe1010f3 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.test.ts @@ -103,6 +103,46 @@ describe('expression params validation', () => { expect(validateExpression(initialParams).errors.termField[0]).toBe('Term field is required.'); }); + test('if termField property is an array but has no items should return proper error message', () => { + const initialParams: EsQueryRuleParams = { + index: ['test'], + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + timeField: '', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'top', + termSize: 10, + termField: [], + }; + expect(validateExpression(initialParams).errors.termField.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.termField[0]).toBe('Term field is required.'); + }); + + test('if termField property is an array but has more than 4 items, should return proper error message', () => { + const initialParams: EsQueryRuleParams = { + index: ['test'], + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + timeWindowSize: 1, + timeWindowUnit: 's', + threshold: [0], + timeField: '', + excludeHitsFromPreviousRun: true, + aggType: 'count', + groupBy: 'top', + termSize: 10, + termField: ['term', 'term2', 'term3', 'term4', 'term5'], + }; + expect(validateExpression(initialParams).errors.termField.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.termField[0]).toBe( + 'Cannot select more than 4 terms' + ); + }); + test('if esQuery property is invalid JSON should return proper error message', () => { const initialParams: EsQueryRuleParams = { index: ['test'], diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts index f2d1de5ef8695..5830a506a0073 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/validation.ts @@ -14,6 +14,7 @@ import { builtInGroupByTypes, COMPARATORS, } from '@kbn/triggers-actions-ui-plugin/public'; +import { MAX_SELECTABLE_GROUP_BY_TERMS } from '../../../common/constants'; import { EsQueryRuleParams, SearchType } from './types'; import { isEsqlQueryRule, isSearchSourceRule } from './util'; import { @@ -72,7 +73,7 @@ const validateCommonParams = (ruleParams: EsQueryRuleParams) => { groupBy && builtInGroupByTypes[groupBy].validNormalizedTypes && builtInGroupByTypes[groupBy].validNormalizedTypes.length > 0 && - !termField + (!termField || termField.length <= 0) ) { errors.termField.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredTermFieldText', { @@ -81,6 +82,22 @@ const validateCommonParams = (ruleParams: EsQueryRuleParams) => { ); } + if ( + groupBy && + builtInGroupByTypes[groupBy].validNormalizedTypes && + builtInGroupByTypes[groupBy].validNormalizedTypes.length > 0 && + termField && + Array.isArray(termField) && + termField.length > MAX_SELECTABLE_GROUP_BY_TERMS + ) { + errors.termField.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.overNumberedTermFieldText', { + defaultMessage: `Cannot select more than {max} terms`, + values: { max: MAX_SELECTABLE_GROUP_BY_TERMS }, + }) + ); + } + if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredThreshold0Text', { diff --git a/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts index 83679f34fbb53..4e539d1f41784 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/threshold/types.ts @@ -34,7 +34,7 @@ export interface IndexThresholdRuleParams extends RuleTypeParams { aggField?: string; groupBy?: string; termSize?: number; - termField?: string; + termField?: string | string[]; thresholdComparator?: string; threshold: number[]; timeWindowSize: number; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts index 7e99ded206b2c..b6011e72bbcc7 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.test.ts @@ -179,15 +179,51 @@ describe('ruleType Params validate()', () => { }); it('fails for invalid termField', async () => { + params.termField = ['term', 'term 2']; + params.termSize = 1; + expect(onValidate()).not.toThrow(); + + params.termField = 'term'; + params.termSize = 1; + expect(onValidate()).not.toThrow(); + + // string or array of string params.groupBy = 'top'; params.termField = 42; - expect(onValidate()).toThrowErrorMatchingInlineSnapshot( - `"[termField]: expected value of type [string] but got [number]"` + expect(onValidate()).toThrow(`[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [number] +- [termField.1]: expected value of type [array] but got [number]`); + + // no array other than array of stings + params.termField = [1, 2, 3]; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1.0]: expected value of type [string] but got [number]` ); + // no empty string params.termField = ''; - expect(onValidate()).toThrowErrorMatchingInlineSnapshot( - `"[termField]: value has length [0] but it must have a minimum length of [1]."` + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: value has length [0] but it must have a minimum length of [1]. +- [termField.1]: could not parse array value from json input` + ); + + // no array with one element -> has to be a string + params.termField = ['term']; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1]: array size is [1], but cannot be smaller than [2]` + ); + + // no array that has more than 4 elements + params.termField = ['term', 'term2', 'term3', 'term4', 'term4']; + expect(onValidate()).toThrow( + `[termField]: types that failed validation: +- [termField.0]: expected value of type [string] but got [Array] +- [termField.1]: array size is [5], but cannot be greater than [4]` ); }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts index 1897ebad6c1ee..5469c6fa60247 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type_params.ts @@ -15,6 +15,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/server'; import { RuleTypeState } from '@kbn/alerting-plugin/server'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { MAX_SELECTABLE_GROUP_BY_TERMS } from '../../../common/constants'; import { ComparatorFnNames } from '../../../common'; import { Comparator } from '../../../common/comparator_types'; import { getComparatorSchemaType } from '../lib/comparator'; @@ -48,7 +49,12 @@ const EsQueryRuleParamsSchemaProperties = { // how to group groupBy: schema.string({ validate: validateGroupBy, defaultValue: 'all' }), // field to group on (for groupBy: top) - termField: schema.maybe(schema.string({ minLength: 1 })), + termField: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string(), { minSize: 2, maxSize: MAX_SELECTABLE_GROUP_BY_TERMS }), + ]) + ), // limit on number of groups returned termSize: schema.maybe(schema.number({ min: 1 })), searchType: schema.oneOf( diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index c65ac1eff0347..4678324fdbef4 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -535,10 +535,10 @@ Props definition: interface GroupByExpressionProps { groupBy: string; termSize?: number; - termField?: string; + termField?: string | string[]; errors: { [key: string]: string[] }; onChangeSelectedTermSize: (selectedTermSize?: number) => void; - onChangeSelectedTermField: (selectedTermField?: string) => void; + onChangeSelectedTermField: (selectedTermField?: string | string[]) => void; onChangeSelectedGroupBy: (selectedGroupBy?: string) => void; fields: Record; customGroupByTypes?: { @@ -555,9 +555,9 @@ interface GroupByExpressionProps { | termSize | Selected term size that will be set as the alert type property. | | termField | Selected term field that will be set as the alert type property. | | errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`. | -| onChangeSelectedTermSize | Event handler that will be excuted if selected term size is changed. | -| onChangeSelectedTermField | Event handler that will be excuted if selected term field is changed. | -| onChangeSelectedGroupBy | Event handler that will be excuted if selected group by is changed. | +| onChangeSelectedTermSize | Event handler that will be executed if selected term size is changed. | +| onChangeSelectedTermField | Event handler that will be executed if selected term field is changed. | +| onChangeSelectedGroupBy | Event handler that will be executed if selected group by is changed. | | fields | Fields list with options for the `termField` dropdown. | | customGroupByTypes | (Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`. | | popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts index 42278057d124c..1925954ea7bb9 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.test.ts @@ -247,6 +247,67 @@ describe('buildAgg', () => { }); }); + it('should create correct aggregation when condition params are defined and timeSeries is defined and multi terms selected', async () => { + expect( + buildAggregation({ + timeSeries: { + timeField: 'time-field', + timeWindowSize: 5, + timeWindowUnit: 'm', + dateStart: '2021-04-22T15:19:31Z', + dateEnd: '2021-04-22T15:20:31Z', + interval: '1m', + }, + aggType: 'count', + aggField: undefined, + termField: ['the-term', 'second-term'], + termSize: 10, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }) + ).toEqual({ + groupAgg: { + multi_terms: { + size: 10, + terms: [{ field: 'the-term' }, { field: 'second-term' }], + }, + aggs: { + conditionSelector: { + bucket_selector: { + buckets_path: { + compareValue: '_count', + }, + script: `params.compareValue > 1`, + }, + }, + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + }, + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + }); + }); + it('should create correct aggregation when condition params are defined and timeSeries is undefined', async () => { expect( buildAggregation({ diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts index 5362682e0b848..2c6ccac04c638 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/build_agg.ts @@ -19,7 +19,7 @@ export interface BuildAggregationOpts { aggType: string; aggField?: string; termSize?: number; - termField?: string; + termField?: string | string[]; topHitsSize?: number; condition?: { resultLimit?: number; @@ -32,7 +32,7 @@ export const BUCKET_SELECTOR_FIELD = `params.${BUCKET_SELECTOR_PATH_NAME}`; export const DEFAULT_GROUPS = 100; export const isCountAggregation = (aggType: string) => aggType === 'count'; -export const isGroupAggregation = (termField?: string) => !!termField; +export const isGroupAggregation = (termField?: string | string[]) => !!termField; export const buildAggregation = ({ timeSeries, @@ -48,6 +48,7 @@ export const buildAggregation = ({ }; const isCountAgg = isCountAggregation(aggType); const isGroupAgg = isGroupAggregation(termField); + const isMultiTerms = Array.isArray(termField); const isDateAgg = !!timeSeries; const includeConditionInQuery = !!condition; @@ -82,10 +83,19 @@ export const buildAggregation = ({ if (isGroupAgg) { aggParent.aggs = { groupAgg: { - terms: { - field: termField, - size: terms, - }, + ...(isMultiTerms + ? { + multi_terms: { + terms: termField.map((field) => ({ field })), + size: terms, + }, + } + : { + terms: { + field: termField, + size: terms, + }, + }), }, ...(includeConditionInQuery ? { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.test.tsx index 07c1378eee85e..a80d3ac3299cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.test.tsx @@ -6,146 +6,216 @@ */ import * as React from 'react'; -import { shallow } from 'enzyme'; -import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; -import { act } from 'react-dom/test-utils'; import { GroupByExpression } from './group_by_over'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { render, screen, fireEvent, configure } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; describe('group by expression', () => { - it('renders with builtin group by types', () => { + configure({ testIdAttribute: 'data-test-subj' }); + it('renders with builtin group by types', async () => { const onChangeSelectedTermField = jest.fn(); const onChangeSelectedGroupBy = jest.fn(); const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + + ); - expect(wrapper.find('[data-test-subj="overExpressionSelect"]')).toMatchInlineSnapshot(` - - `); + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByRole('dialog')).toBeInTheDocument(); + + expect(screen.getByRole('option', { name: 'all documents' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'top' })).toBeInTheDocument(); }); - it('renders with aggregation type fields', () => { + it('clears selected agg field if fields does not contain current selection', async () => { const onChangeSelectedTermField = jest.fn(); - const onChangeSelectedGroupBy = jest.fn(); - const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + /> + ); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(undefined); + }); - expect(wrapper.find('[data-test-subj="fieldsExpressionSelect"]')).toMatchInlineSnapshot(` - { + const onChangeSelectedTermField = jest.fn(); + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} /> - `); + + ); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(undefined); }); - it('renders with default aggreagation type preselected if no aggType was set', () => { + it('clears selected agg field if groupBy field is all', async () => { const onChangeSelectedTermField = jest.fn(); - const onChangeSelectedGroupBy = jest.fn(); - const onChangeSelectedTermSize = jest.fn(); - const wrapper = shallow( - + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + /> + ); - wrapper.simulate('click'); - expect(wrapper.find('[value="all"]').length > 0).toBeTruthy(); - expect( - wrapper.contains( - { + const onChangeSelectedTermField = jest.fn(); + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} /> - ) - ).toBeTruthy(); + + ); + + expect(onChangeSelectedTermField).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByText(/You are in a dialog/)).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); + + const option1 = screen.getByText('field1'); + expect(option1).toBeInTheDocument(); + fireEvent.click(option1); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field1'); + + const option2 = screen.getByText('field2'); + expect(option2).toBeInTheDocument(); + fireEvent.click(option2); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field2'); }); - it('clears selected agg field if fields does not contain current selection', async () => { + it('calls onChangeSelectedTermField when multiple termFields are selected', async () => { const onChangeSelectedTermField = jest.fn(); - const wrapper = mountWithIntl( - {}} - onChangeSelectedTermSize={() => {}} - onChangeSelectedTermField={onChangeSelectedTermField} - /> + render( + + {}} + onChangeSelectedTermSize={() => {}} + onChangeSelectedTermField={onChangeSelectedTermField} + canSelectMultiTerms={true} + /> + ); + expect(onChangeSelectedTermField).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('groupByExpression')); + + expect(await screen.findByText(/You are in a dialog/)).toBeInTheDocument(); + + // dropdown is closed + expect(screen.queryByText('field1')).not.toBeInTheDocument(); + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); - await act(async () => { - await nextTick(); - wrapper.update(); - }); + // dropdown is open + expect(screen.getByText('field1')).toBeInTheDocument(); + fireEvent.click(screen.getByText('field1')); + expect(onChangeSelectedTermField).toHaveBeenCalledWith('field1'); - expect(onChangeSelectedTermField).toHaveBeenCalledWith(''); + fireEvent.click(screen.getByText('field2')); + expect(onChangeSelectedTermField).toHaveBeenCalledTimes(2); + expect(onChangeSelectedTermField).toHaveBeenCalledWith(['field1', 'field2']); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 0819f7541d1cc..1a6901ffb5dec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; @@ -17,6 +17,8 @@ import { EuiFormRow, EuiSelect, EuiFieldNumber, + EuiComboBoxOptionOption, + EuiComboBox, } from '@elastic/eui'; import { builtInGroupByTypes } from '../constants'; import { FieldOption, GroupByType } from '../types'; @@ -24,18 +26,17 @@ import { ClosablePopoverTitle } from './components'; import { IErrorObject } from '../../types'; interface GroupByOverFieldOption { - text: string; - value: string; + label: string; } export interface GroupByExpressionProps { groupBy: string; errors: IErrorObject; onChangeSelectedTermSize: (selectedTermSize?: number) => void; - onChangeSelectedTermField: (selectedTermField?: string) => void; + onChangeSelectedTermField: (selectedTermField?: string | string[]) => void; onChangeSelectedGroupBy: (selectedGroupBy?: string) => void; fields: FieldOption[]; termSize?: number; - termField?: string; + termField?: string | string[]; customGroupByTypes?: { [key: string]: GroupByType; }; @@ -53,6 +54,7 @@ export interface GroupByExpressionProps { | 'rightUp' | 'rightDown'; display?: 'fullWidth' | 'inline'; + canSelectMultiTerms?: boolean; } export const GroupByExpression = ({ @@ -67,45 +69,55 @@ export const GroupByExpression = ({ termField, customGroupByTypes, popupPosition, + canSelectMultiTerms, }: GroupByExpressionProps) => { const groupByTypes = customGroupByTypes ?? builtInGroupByTypes; const [groupByPopoverOpen, setGroupByPopoverOpen] = useState(false); const MIN_TERM_SIZE = 1; const MAX_TERM_SIZE = 1000; - const firstFieldOption: GroupByOverFieldOption = { - text: i18n.translate( - 'xpack.triggersActionsUI.common.expressionItems.groupByType.timeFieldOptionLabel', - { - defaultMessage: 'Select a field', - } - ), - value: '', - }; - const availableFieldOptions: GroupByOverFieldOption[] = fields.reduce( - (options: GroupByOverFieldOption[], field: FieldOption) => { - if (groupByTypes[groupBy].validNormalizedTypes.includes(field.normalizedType)) { - options.push({ - text: field.name, - value: field.name, - }); - } - return options; - }, - [firstFieldOption] + const availableFieldOptions: GroupByOverFieldOption[] = useMemo( + () => + fields.reduce((options: GroupByOverFieldOption[], field: FieldOption) => { + if (groupByTypes[groupBy].validNormalizedTypes.includes(field.normalizedType)) { + options.push({ label: field.name }); + } + return options; + }, []), + [groupByTypes, fields, groupBy] ); + const initialTermFieldOptions = useMemo(() => { + let initialFields: string[] = []; + + if (!!termField) { + initialFields = Array.isArray(termField) ? termField : [termField]; + } + return initialFields.map((field: string) => ({ + label: field, + })); + }, [termField]); + + const [selectedTermsFieldsOptions, setSelectedTermsFieldsOptions] = + useState(initialTermFieldOptions); + + useEffect(() => { + if (groupBy === builtInGroupByTypes.all.value && selectedTermsFieldsOptions.length > 0) { + setSelectedTermsFieldsOptions([]); + onChangeSelectedTermField(undefined); + } + }, [selectedTermsFieldsOptions, groupBy, onChangeSelectedTermField]); + useEffect(() => { // if current field set doesn't contain selected field, clear selection - if ( - termField && - termField.length > 0 && - fields.length > 0 && - !fields.find((field: FieldOption) => field.name === termField) - ) { - onChangeSelectedTermField(''); + const hasUnknownField = selectedTermsFieldsOptions.some( + (fieldOption) => !fields.some((field) => field.name === fieldOption.label) + ); + if (hasUnknownField) { + setSelectedTermsFieldsOptions([]); + onChangeSelectedTermField(undefined); } - }, [termField, fields, onChangeSelectedTermField]); + }, [selectedTermsFieldsOptions, fields, onChangeSelectedTermField]); return ( 0))} /> } isOpen={groupByPopoverOpen} @@ -157,7 +169,7 @@ export const GroupByExpression = ({ /> - + - + 0} error={errors.termSize}> - - 0 && termField !== undefined} - error={errors.termField} - > - + 0} error={errors.termField}> + 0 && termField !== undefined} - onChange={(e) => { - onChangeSelectedTermField(e.target.value); + isInvalid={errors.termField.length > 0} + selectedOptions={selectedTermsFieldsOptions} + onChange={( + selectedOptions: Array> + ) => { + const selectedTermFields = selectedOptions.map((option) => option.label); + + const termsToSave = + Array.isArray(selectedTermFields) && selectedTermFields.length > 1 + ? selectedTermFields + : selectedTermFields[0]; + + onChangeSelectedTermField(termsToSave); + setSelectedTermsFieldsOptions(selectedOptions); }} options={availableFieldOptions} - onBlur={() => { - if (termField === undefined) { - onChangeSelectedTermField(''); - } - }} /> diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts index c0b9113fa6143..25ca53c52e429 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts @@ -368,6 +368,75 @@ export default function ruleTests({ getService }: FtrProviderContext) { }) ); + [ + [ + 'esQuery', + async () => { + await createRule({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '>', + threshold: [-1], + groupBy: 'top', + termField: ['group', 'testedValue'], + termSize: 2, + }); + }, + ] as const, + [ + 'searchSource', + async () => { + const esTestDataView = await indexPatterns.create( + { title: ES_TEST_INDEX_NAME, timeFieldName: 'date' }, + { override: true }, + getUrlPrefix(Spaces.space1.id) + ); + await createRule({ + name: 'always fire', + size: 100, + thresholdComparator: '>', + threshold: [-1], + searchType: 'searchSource', + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: esTestDataView.id, + filter: [], + }, + groupBy: 'top', + termField: ['group', 'testedValue'], + termSize: 2, + }); + }, + ] as const, + ].forEach(([searchType, initData]) => + it(`runs correctly: threshold on grouped with multi term hit count < > for ${searchType} search type`, async () => { + // write documents from now to the future end date in groups + await createGroupedEsDocumentsInGroups(ES_GROUPS_TO_WRITE, endDate); + await initData(); + + const docs = await waitForDocs(2); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + const titlePattern = /rule 'always fire' matched query for group group-\d/; + expect(title).to.match(titlePattern); + const messagePattern = + /rule 'always fire' is active:\n\n- Value: \d+\n- Conditions Met: Number of matching documents for group \"group-\d,\d{1,2}\" is greater than -1 over 20s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).not.to.be.empty(); + + expect(previousTimestamp).to.be.empty(); + } + }) + ); + [ [ 'esQuery', @@ -1044,7 +1113,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { aggType?: string; aggField?: string; groupBy?: string; - termField?: string; + termField?: string | string[]; termSize?: number; } From fb2533eacab10244439559fffeee6265c66315b1 Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Mon, 18 Sep 2023 23:52:44 +0200 Subject: [PATCH 33/58] [Cloud Security] fix agent version on cloudformation template in Cloud Security integrations (#166485) ## Summary fixes - https://github.com/elastic/security-team/issues/7557 using the new `useAgentVersion` hook to get the agent version to prefill in the Cloudformation template There is already a PR with the same fix https://github.com/elastic/kibana/pull/166198 but as it also changes the logic of `useAgentVersion` itself, it might take some time to align. This PR only fixes the immediate issue we have in Serverless --- .../hooks/use_create_cloud_formation_url.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/fleet/public/hooks/use_create_cloud_formation_url.ts b/x-pack/plugins/fleet/public/hooks/use_create_cloud_formation_url.ts index 45e5b259afd15..9e5c2008f262c 100644 --- a/x-pack/plugins/fleet/public/hooks/use_create_cloud_formation_url.ts +++ b/x-pack/plugins/fleet/public/hooks/use_create_cloud_formation_url.ts @@ -12,7 +12,7 @@ import type { CloudSecurityIntegrationAwsAccountType, } from '../components/agent_enrollment_flyout/types'; -import { useKibanaVersion } from './use_kibana_version'; +import { useAgentVersion } from './use_agent_version'; import { useGetSettings } from './use_request'; const CLOUD_FORMATION_DEFAULT_ACCOUNT_TYPE = 'single-account'; @@ -26,7 +26,7 @@ export const useCreateCloudFormationUrl = ({ }) => { const { data, isLoading } = useGetSettings(); - const kibanaVersion = useKibanaVersion(); + const agentVersion = useAgentVersion(); let isError = false; let error: string | undefined; @@ -49,12 +49,12 @@ export const useCreateCloudFormationUrl = ({ } const cloudFormationUrl = - enrollmentAPIKey && fleetServerHost && cloudFormationProps?.templateUrl + enrollmentAPIKey && fleetServerHost && cloudFormationProps?.templateUrl && agentVersion ? createCloudFormationUrl( cloudFormationProps?.templateUrl, enrollmentAPIKey, fleetServerHost, - kibanaVersion, + agentVersion, cloudFormationProps?.awsAccountType ) : undefined; @@ -71,15 +71,18 @@ const createCloudFormationUrl = ( templateURL: string, enrollmentToken: string, fleetUrl: string, - kibanaVersion: string, + agentVersion: string, awsAccountType: CloudSecurityIntegrationAwsAccountType | undefined ) => { let cloudFormationUrl; + /* + template url has `¶m_ElasticAgentVersion=KIBANA_VERSION` part. KIBANA_VERSION is used for templating as agent version used to match Kibana version, but now it's not necessarily the case + */ cloudFormationUrl = templateURL .replace('FLEET_ENROLLMENT_TOKEN', enrollmentToken) .replace('FLEET_URL', fleetUrl) - .replace('KIBANA_VERSION', kibanaVersion); + .replace('KIBANA_VERSION', agentVersion); if (cloudFormationUrl.includes('ACCOUNT_TYPE')) { cloudFormationUrl = cloudFormationUrl.replace( From b7ba000b86304cccd999e112abc7b41bd4832a7e Mon Sep 17 00:00:00 2001 From: Bree Hall <40739624+breehall@users.noreply.github.com> Date: Mon, 18 Sep 2023 18:23:59 -0400 Subject: [PATCH 34/58] Upgrade EUI to v88.3.0 (#166292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EUI `88.2.0` ➡️ `88.3.0` ## [`88.3.0`](https://github.com/elastic/eui/tree/v88.3.0) - `EuiGlobalToastList` now shows a "Clear all" button by default once above a certain number of toasts (defaults to 3). This threshold is configurable with the `showClearAllButtonAt` prop ([#7111](https://github.com/elastic/eui/pull/7111)) - Added an optional `onClearAllToasts` callback to `EuiGlobalToastList` ([#7111](https://github.com/elastic/eui/pull/7111)) - Added the `value`, `onChange`, and `onCancel` props that allow `EuiInlineEdit` to be used as a controlled component ([#7157](https://github.com/elastic/eui/pull/7157)) - Added `grabOmnidirectional`, `transitionLeftIn`, `transitionLeftOut`, `transitionTopIn`, and `transitionTopOut` icon glyphs. ([#7168](https://github.com/elastic/eui/pull/7168)) **Bug fixes** - Fixed `EuiInlineEdit` components to correctly spread `...rest` attributes to the parent wrapper ([#7157](https://github.com/elastic/eui/pull/7157)) - Fixed `EuiListGroupItem` to correctly render the `extraAction` button when `showToolTip` is also passed ([#7159](https://github.com/elastic/eui/pull/7159)) **Dependency updates** - Updated `@hello-pangea/dnd` to v16.3.0 ([#7125](https://github.com/elastic/eui/pull/7125)) - Updated `@types/lodash` to v4.14.198 ([#7126](https://github.com/elastic/eui/pull/7126)) **Accessibility** - `EuiAccordion` now correctly respects reduced motion settings ([#7161](https://github.com/elastic/eui/pull/7161)) - `EuiAccordion` now shows a focus outline to keyboard users around its revealed children on open ([#7161](https://github.com/elastic/eui/pull/7161)) **CSS-in-JS conversions** - Converted `EuiSplitPanel` to Emotion ([#7172](https://github.com/elastic/eui/pull/7172)) ⚠️ As a quick heads up, serverless tests appear to have been extremely flake/failure-prone the last couple weeks, particularly Cypress tests. We've evaluated the listed failures and fixed ones that were related to changes in this PR, and we're relatively confident the remaining failures are not related to changes from EUI. Please let us know if you think this is not the case. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cee Chen Co-authored-by: Jon --- package.json | 2 +- .../collapsible_nav.test.tsx.snap | 389 +++++++------ .../__snapshots__/i18n_service.test.tsx.snap | 4 +- .../src/i18n_eui_mapping.tsx | 21 +- packages/kbn-cypress-config/index.ts | 13 + .../__snapshots__/comments.test.tsx.snap | 514 +++++++++--------- .../kbn-ui-shared-deps-npm/webpack.config.js | 2 +- src/dev/license_checker/config.ts | 2 +- .../page_objects/unified_field_list.ts | 2 +- .../snapshots/session/combined_test0.json | 1 - .../snapshots/session/combined_test1.json | 1 - .../snapshots/session/combined_test2.json | 1 - .../snapshots/session/combined_test3.json | 1 - .../snapshots/session/essql.json | 1 - .../snapshots/session/final_output_test.json | 1 - .../snapshots/session/metric_all_data.json | 1 - .../snapshots/session/metric_empty_data.json | 1 - .../session/metric_invalid_data.json | 1 - .../session/metric_multi_metric_data.json | 1 - .../session/metric_percentage_mode.json | 1 - .../session/metric_single_metric_data.json | 1 - .../snapshots/session/partial_test_1.json | 1 - .../snapshots/session/partial_test_2.json | 1 - .../snapshots/session/step_output_test0.json | 1 - .../snapshots/session/step_output_test1.json | 1 - .../snapshots/session/step_output_test2.json | 1 - .../snapshots/session/step_output_test3.json | 1 - .../snapshots/session/tagcloud_all_data.json | 1 - .../session/tagcloud_empty_data.json | 1 - .../snapshots/session/tagcloud_fontsize.json | 1 - .../session/tagcloud_invalid_data.json | 1 - .../session/tagcloud_metric_data.json | 1 - .../snapshots/session/tagcloud_options.json | 1 - .../public/components/chat/chat_item.tsx | 8 +- .../app/custom_logs/configure_logs.tsx | 2 +- ...screen_capture_panel_content.test.tsx.snap | 270 ++++----- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../lib/check_action_type_enabled.scss | 4 +- yarn.lock | 47 +- 41 files changed, 636 insertions(+), 671 deletions(-) delete mode 100644 test/interpreter_functional/snapshots/session/combined_test0.json delete mode 100644 test/interpreter_functional/snapshots/session/combined_test1.json delete mode 100644 test/interpreter_functional/snapshots/session/combined_test2.json delete mode 100644 test/interpreter_functional/snapshots/session/combined_test3.json delete mode 100644 test/interpreter_functional/snapshots/session/essql.json delete mode 100644 test/interpreter_functional/snapshots/session/final_output_test.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_all_data.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_empty_data.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_invalid_data.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_multi_metric_data.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_percentage_mode.json delete mode 100644 test/interpreter_functional/snapshots/session/metric_single_metric_data.json delete mode 100644 test/interpreter_functional/snapshots/session/partial_test_1.json delete mode 100644 test/interpreter_functional/snapshots/session/partial_test_2.json delete mode 100644 test/interpreter_functional/snapshots/session/step_output_test0.json delete mode 100644 test/interpreter_functional/snapshots/session/step_output_test1.json delete mode 100644 test/interpreter_functional/snapshots/session/step_output_test2.json delete mode 100644 test/interpreter_functional/snapshots/session/step_output_test3.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_all_data.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_empty_data.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_fontsize.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_metric_data.json delete mode 100644 test/interpreter_functional/snapshots/session/tagcloud_options.json diff --git a/package.json b/package.json index 6ebe1726de727..9692d7f7afc24 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "88.2.0", + "@elastic/eui": "88.3.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 0ae5e0504839b..ec57877a9744e 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -109,7 +109,7 @@ Array [ data-test-subj="collapsibleNavGroup-recentlyViewed" >
@@ -546,7 +545,7 @@ Object { data-test-subj="exceptionItemCardComments" >
diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index a7beaf4462dc8..df2ed2071dc1c 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -146,7 +146,7 @@ module.exports = (_, argv) => { // @hello-pangea/dnd emits optional chaining that confuses webpack. // We need to transform it using babel before going further { - test: /@hello-pangea\/dnd\/dist\/dnd\.js$/, + test: /@hello-pangea\/dnd\/dist\/dnd(\.esm)?\.js$/, use: [ { loader: 'babel-loader', diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index d7c04f430661c..a89f994dc6bd0 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -85,7 +85,7 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.4.0': ['Elastic License 2.0'], - '@elastic/eui@88.2.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@88.3.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry 'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary }; diff --git a/test/functional/page_objects/unified_field_list.ts b/test/functional/page_objects/unified_field_list.ts index caa0c7f24dada..5e306239dfdcd 100644 --- a/test/functional/page_objects/unified_field_list.ts +++ b/test/functional/page_objects/unified_field_list.ts @@ -84,7 +84,7 @@ export class UnifiedFieldListPageObject extends FtrService { public async toggleSidebarSection(sectionName: SidebarSectionName) { return await this.find.clickByCssSelector( - `${this.getSidebarSectionSelector(sectionName, true)} .euiAccordion__iconButton` + `${this.getSidebarSectionSelector(sectionName, true)} .euiAccordion__arrow` ); } diff --git a/test/interpreter_functional/snapshots/session/combined_test0.json b/test/interpreter_functional/snapshots/session/combined_test0.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test0.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test1.json b/test/interpreter_functional/snapshots/session/combined_test1.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test1.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test2.json b/test/interpreter_functional/snapshots/session/combined_test2.json deleted file mode 100644 index 98eb526aa75f6..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test2.json +++ /dev/null @@ -1 +0,0 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json deleted file mode 100644 index 922d266f7e187..0000000000000 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/essql.json b/test/interpreter_functional/snapshots/session/essql.json deleted file mode 100644 index d89bd10c382bd..0000000000000 --- a/test/interpreter_functional/snapshots/session/essql.json +++ /dev/null @@ -1 +0,0 @@ -{"columns":[{"id":"@timestamp","meta":{"type":"date"},"name":"@timestamp"},{"id":"@message","meta":{"type":"string"},"name":"@message"},{"id":"bytes","meta":{"type":"number"},"name":"bytes"}],"meta":{"type":"essql"},"rows":[{"@message":"143.84.142.7 - - [2015-09-20T00:00:00.000Z] \"GET /uploads/steven-hawley.jpg HTTP/1.1\" 200 1623 \"-\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\"","@timestamp":"2015-09-20T00:00:00.000Z","bytes":1623},{"@message":"193.164.192.47 - - [2015-09-20T00:30:34.206Z] \"GET /uploads/michael-foreman.jpg HTTP/1.1\" 200 8537 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\"","@timestamp":"2015-09-20T00:30:34.206Z","bytes":8537},{"@message":"176.7.244.68 - - [2015-09-20T00:32:42.058Z] \"GET /uploads/james-pawelczyk.jpg HTTP/1.1\" 200 9196 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"","@timestamp":"2015-09-20T00:32:42.058Z","bytes":9196},{"@message":"237.56.90.184 - - [2015-09-20T00:35:21.445Z] \"GET /uploads/david-leestma.jpg HTTP/1.1\" 200 9790 \"-\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\"","@timestamp":"2015-09-20T00:35:21.445Z","bytes":9790},{"@message":"255.56.89.50 - - [2015-09-20T00:43:01.353Z] \"GET /uploads/michael-r-barratt.jpg HTTP/1.1\" 200 9583 \"-\" \"Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1\"","@timestamp":"2015-09-20T00:43:01.353Z","bytes":9583}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json deleted file mode 100644 index 922d266f7e187..0000000000000 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json deleted file mode 100644 index 3b8553435624f..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"bytes","params":null},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_empty_data.json b/test/interpreter_functional/snapshots/session/metric_empty_data.json deleted file mode 100644 index ef645bdb30afd..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_empty_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_invalid_data.json b/test/interpreter_functional/snapshots/session/metric_invalid_data.json deleted file mode 100644 index daf08c67211cc..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_invalid_data.json +++ /dev/null @@ -1 +0,0 @@ -"[legacyMetricVis] > [visdimension] > Provided column name or index is invalid: 0" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json deleted file mode 100644 index 90d572ab720f0..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json deleted file mode 100644 index 2bb96cbcb4c0a..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":{"colors":["rgb(0,0,0,0)","rgb(100, 100, 100)"],"continuity":"none","gradient":false,"range":"number","rangeMax":10000,"rangeMin":0,"stops":[0,10000]},"percentageMode":true,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json deleted file mode 100644 index 2c9c785e4ab2a..0000000000000 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json deleted file mode 100644 index 6e12a10d1e283..0000000000000 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json deleted file mode 100644 index 922d266f7e187..0000000000000 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test0.json b/test/interpreter_functional/snapshots/session/step_output_test0.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test0.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test1.json b/test/interpreter_functional/snapshots/session/step_output_test1.json deleted file mode 100644 index 8f00d72df8ab3..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test1.json +++ /dev/null @@ -1 +0,0 @@ -{"filters":[],"query":[],"timeRange":null,"type":"kibana_context"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test2.json b/test/interpreter_functional/snapshots/session/step_output_test2.json deleted file mode 100644 index 98eb526aa75f6..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test2.json +++ /dev/null @@ -1 +0,0 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json deleted file mode 100644 index 922d266f7e187..0000000000000 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"legacyMetricVis","type":"render","value":{"canNavigateToLens":false,"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"autoScale":null,"colorFullBackground":false,"labels":{"position":"bottom","show":true,"style":{"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:24px;line-height:1","spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"24px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}},"metricColorMode":"None","palette":null,"percentageMode":false,"style":{"bgColor":false,"css":"font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:center;font-size:60px;line-height:1","labelColor":false,"spec":{"fontFamily":"'Open Sans', Helvetica, Arial, sans-serif","fontSize":"60px","fontStyle":"normal","fontWeight":"normal","lineHeight":"1","textAlign":"center","textDecoration":"none"},"type":"style"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json deleted file mode 100644 index cb14c6ea89407..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json b/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json deleted file mode 100644 index 0910e67409423..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_empty_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json deleted file mode 100644 index 21e213ebb9c27..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"isPreview":false,"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json deleted file mode 100644 index d163fcefecabe..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json +++ /dev/null @@ -1 +0,0 @@ -"[tagcloud] > [visdimension] > Provided column name or index is invalid: 0" \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json deleted file mode 100644 index f340c5b653e35..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json deleted file mode 100644 index ecbafbbc0afba..0000000000000 --- a/test/interpreter_functional/snapshots/session/tagcloud_options.json +++ /dev/null @@ -1 +0,0 @@ -{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"2","indexPatternId":"logstash-*","params":{"excludeIsRegex":true,"field":"response.raw","includeIsRegex":true,"missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"hasPrecisionError":false,"id":"1","indexPatternId":"logstash-*","params":{"emptyAsNull":false},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"meta":{"source":"logstash-*","statistics":{"totalCount":14004},"type":"esaggs"},"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"ariaLabel":null,"bucket":{"accessor":1,"format":{"id":"number"},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"custom","params":{"colors":["#882E72","#B178A6","#D6C1DE","#1965B0","#5289C7","#7BAFDE","#4EB265","#90C987","#CAE0AB","#F7EE55","#F6C141","#F1932D","#E8601C","#DC050C"],"continuity":"above","gradient":false,"range":"percent","rangeMax":null,"rangeMin":0,"stops":[]},"type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx index a7e212f70a8b5..b3fa93d1ec522 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx @@ -61,12 +61,6 @@ const noPanelMessageClassName = css` } `; -const accordionButtonClassName = css` - .euiAccordion__iconButton { - display: none; - } -`; - export function ChatItem({ actions: { canCopy, canEdit, canGiveFeedback, canRegenerate }, display: { collapsed }, @@ -141,7 +135,7 @@ export function ChatItem({ contentElement = ( diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/configure_logs.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/configure_logs.tsx index 1e438e1dd0e66..b5edcf75b1214 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/configure_logs.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/configure_logs.tsx @@ -326,7 +326,7 @@ export function ConfigureLogsContent() { color: euiTheme.colors.primaryText, fontSize: xsFontSize, }, - '.euiAccordion__iconButton svg': { + '.euiAccordion__arrow svg': { stroke: euiTheme.colors.primary, width: euiTheme.size.m, height: euiTheme.size.m, diff --git a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap index 9d95afb5852dd..518c7bb261426 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap +++ b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap @@ -98,13 +98,13 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout data-test-subj="shareReportingAdvancedOptionsButton" >
-
+
+
-
-
+ + Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. + +

+
+
+
+

-

- - Alternatively, copy this POST URL to call generation from outside Kibana or from Watcher. - -

-
-
+